From 1aa300c53f070436888150ae582dad23cddac03b Mon Sep 17 00:00:00 2001 From: David Kupka Date: Mon, 15 Aug 2016 08:01:59 +0200 Subject: [PATCH] schema cache: Store API schema cache in memory Read whole cache into memory and keep it there for lifetime of api object. This removes the need to repetitively open/close the cache and speeds up every access to it. https://fedorahosted.org/freeipa/ticket/6048 Reviewed-By: Jan Cholasta --- ipaclient/remote_plugins/schema.py | 51 +++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py index aadc891750782b0961bc46989e3693d1d3ed0ecb..2fc6cce3eca392447cd4230216900116002934f4 100644 --- a/ipaclient/remote_plugins/schema.py +++ b/ipaclient/remote_plugins/schema.py @@ -3,6 +3,7 @@ # import collections +import contextlib import errno import fcntl import json @@ -315,24 +316,6 @@ class _SchemaObjectPlugin(_SchemaPlugin): schema_key = 'classes' -class _LockedZipFile(zipfile.ZipFile): - """ Add locking to zipfile.ZipFile - Shared lock is used with read mode, exclusive with write mode. - """ - def __enter__(self): - if 'r' in self.mode: - fcntl.flock(self.fp, fcntl.LOCK_SH) - elif 'w' in self.mode or 'a' in self.mode: - fcntl.flock(self.fp, fcntl.LOCK_EX) - - return super(_LockedZipFile, self).__enter__() - - def __exit__(self, type_, value, traceback): - fcntl.flock(self.fp, fcntl.LOCK_UN) - - return super(_LockedZipFile, self).__exit__(type_, value, traceback) - - class _SchemaNameSpace(collections.Mapping): def __init__(self, schema, name): @@ -450,6 +433,7 @@ class Schema(object): self._dict = {} self._namespaces = {} self._help = None + self._file = six.StringIO() for ns in self.namespaces: self._dict[ns] = {} @@ -486,9 +470,20 @@ class Schema(object): except AttributeError: pass - def _open_schema(self, filename, mode): + @contextlib.contextmanager + def _open(self, filename, mode): path = os.path.join(self._DIR, filename) - return _LockedZipFile(path, mode) + + with open(path, mode) as f: + if mode.startswith('r'): + fcntl.flock(f, fcntl.LOCK_SH) + else: + fcntl.flock(f, fcntl.LOCK_EX) + + try: + yield f + finally: + fcntl.flock(f, fcntl.LOCK_UN) def _fetch(self, client): if not client.isconnected(): @@ -523,7 +518,11 @@ class Schema(object): self._expiration = ttl + time.time() def _read_schema(self): - with self._open_schema(self._fingerprint, 'r') as schema: + self._file.truncate(0) + with self._open(self._fingerprint, 'r') as f: + self._file.write(f.read()) + + with zipfile.ZipFile(self._file, 'r') as schema: self._dict['fingerprint'] = self._fingerprint for name in schema.namelist(): @@ -566,7 +565,8 @@ class Schema(object): logger.warning("Failed to write schema: {}".format(e)) return - with self._open_schema(self._fingerprint, 'w') as schema: + self._file.truncate(0) + with zipfile.ZipFile(self._file, 'w', zipfile.ZIP_DEFLATED) as schema: for key, value in self._dict.items(): if key in self.namespaces: ns = value @@ -579,8 +579,13 @@ class Schema(object): schema.writestr('_help', json.dumps(self._generate_help(self._dict))) + self._file.seek(0) + with self._open(self._fingerprint, 'w') as f: + f.truncate(0) + f.write(self._file.read()) + def _read(self, path): - with self._open_schema(self._fingerprint, 'r') as zf: + with zipfile.ZipFile(self._file, 'r') as zf: return json.loads(zf.read(path)) def read_namespace_member(self, namespace, member): -- 2.7.4