diff --git a/libraries/entropy/client/interfaces/client.py b/libraries/entropy/client/interfaces/client.py index f3e3839f1..2e8b82ce9 100644 --- a/libraries/entropy/client/interfaces/client.py +++ b/libraries/entropy/client/interfaces/client.py @@ -757,8 +757,9 @@ class Client(Singleton, TextInterface, LoadersMixin, CacheMixin, CalculatorsMixi """ self.__instance_destroyed = True if hasattr(self, '_installed_repository'): - if self._installed_repository != None: - self._installed_repository.close() + if self._installed_repository is not None: + self._installed_repository.close( + _token = etpConst['clientdbid']) if hasattr(self, 'logger'): self.logger.close() if hasattr(self, '_settings') and \ diff --git a/libraries/entropy/client/interfaces/db.py b/libraries/entropy/client/interfaces/db.py index ea4e72e18..0f15eb0d9 100644 --- a/libraries/entropy/client/interfaces/db.py +++ b/libraries/entropy/client/interfaces/db.py @@ -79,8 +79,38 @@ class ClientEntropyRepositoryPlugin(EntropyRepositoryPlugin): return 0 +class CachedRepository(EntropyRepository): + """ + This kind of repository cannot have close() called directly, without + a valid token passed. This is because the class object is cached somewhere + and calling close() would turn into a software bug. + """ + def setCloseToken(self, token): + """ + Set a token that can be used to validate close() calls. Calling + close() on these repos is prohibited and considered a software bug. + Only Entropy Client should be able to close them. + """ + self._close_token = token -class InstalledPackagesRepository(EntropyRepository): + def close(self, _token = None): + """ + Reimplemented from EntropyRepository + """ + close_token = getattr(self, "_close_token", None) + if close_token is not None: + if (_token is None) or (_token != close_token): + raise PermissionDenied( + "cannot close this repository directly. Software bug!") + return EntropyRepository.close(self) + + def __del__(self): + """ + Cannot honor the constraint in this case, sorry! + """ + return EntropyRepository.close(self) + +class InstalledPackagesRepository(CachedRepository): """ This class represents the installed packages repository and is a direct subclass of EntropyRepository. @@ -2387,7 +2417,7 @@ class MaskableRepository(EntropyRepositoryBase): return -1, myr -class AvailablePackagesRepository(EntropyRepository, MaskableRepository): +class AvailablePackagesRepository(CachedRepository, MaskableRepository): """ This class represents the available packages repository and is a direct subclass of EntropyRepository. It implements the update() method in order @@ -2467,10 +2497,14 @@ class AvailablePackagesRepository(EntropyRepository, MaskableRepository): EntropyRepository.clearCache(self) -class GenericRepository(EntropyRepository, MaskableRepository): +class GenericRepository(CachedRepository, MaskableRepository): """ This class represents a generic packages repository and is a direct subclass of EntropyRepository. + Even GenericRepository is a CachedRepository because its object could + get cached by 3rd party. Actually, we require this because our installed + packages repository could end up being a GenericRepository, when running + in fail-safe mode. """ def handlePackage(self, pkg_data, forcedRevision = -1, diff --git a/libraries/entropy/client/interfaces/methods.py b/libraries/entropy/client/interfaces/methods.py index ddd5e2169..f739eaebd 100644 --- a/libraries/entropy/client/interfaces/methods.py +++ b/libraries/entropy/client/interfaces/methods.py @@ -62,9 +62,12 @@ class RepositoryMixin: def ensure_closed_repo(repoid): key = self.__get_repository_cache_key(repoid) for cache_obj in (self._repodb_cache, self._memory_db_instances): + obj = cache_obj.pop(key, None) + if obj is None: + continue try: - cache_obj.pop(key).close() - except (KeyError, AttributeError, OperationalError): + obj.close(_token = repoid) + except OperationalError: pass t2 = _("Please update your repositories now in order to remove this message!") @@ -149,6 +152,7 @@ class RepositoryMixin: def close_repositories(self, mask_clear = True): for item in sorted(self._repodb_cache.keys()): + repository_id, root = item # in-memory repositories cannot be closed # otherwise everything will be lost, to # effectively close these repos you @@ -156,7 +160,7 @@ class RepositoryMixin: if item in self._memory_db_instances: continue try: - self._repodb_cache.pop(item).close() + self._repodb_cache.pop(item).close(_token = repository_id) except OperationalError as err: # wtf! sys.stderr.write("!!! Cannot close Entropy repos: %s\n" % ( err,)) @@ -265,6 +269,7 @@ class RepositoryMixin: xcache = xcache, indexing = indexing ) + conn.setCloseToken(repoid) self._add_plugin_to_client_repository(conn) if (repoid not in self._treeupdates_repos) and \ @@ -788,6 +793,7 @@ class RepositoryMixin: name = etpConst['clientdbid'], xcache = self.xcache, indexing = self.indexing ) + conn.setCloseToken(etpConst['clientdbid']) self._add_plugin_to_client_repository(conn) # TODO: remove this in future, drop useless data from clientdb except (DatabaseError,): @@ -800,7 +806,7 @@ class RepositoryMixin: conn.validate() except SystemDatabaseError: try: - conn.close() + conn.close(_token = etpConst['clientdbid']) except (RepositoryPluginError, OSError, IOError): pass entropy.tools.print_traceback(f = self.logger) @@ -810,7 +816,7 @@ class RepositoryMixin: return conn def reopen_installed_repository(self): - self._installed_repository.close() + self._installed_repository.close(_token = etpConst['clientdbid']) self._open_installed_repository() # make sure settings are in sync self._settings.clear()