def testKeyError(self): """ KeyError is raised as a tuple and not expection. Re-raising it should be aware of this fact and handled carfully. """ try: with utils.RollbackContext(): {}['aKey'] except KeyError: return except Exception: self.fail("Wrong exception was raised") self.fail("Exception was not raised")
def testRaise(self): """ Test that raising an exception in a deferred action does not block all subsequent actions from running """ try: with utils.RollbackContext() as rollback: rollback.prependDefer(self._callDef) rollback.prependDefer(self._raiseDef) rollback.prependDefer(self._callDef) except Exception: self.assertEquals(self._called, 2) return self.fail("Exception was not raised")
def testFirstUndoException(self): """ Test that if multiple actions raise an exception only the first one is raised. When performing a batch rollback operations, probably the first exception is the root cause. """ try: with utils.RollbackContext() as rollback: rollback.prependDefer(self._callDef) rollback.prependDefer(self._raiseDef) rollback.prependDefer(self._callDef) rollback.prependDefer(self._raiseDef, RuntimeError()) rollback.prependDefer(self._callDef) except RuntimeError: self.assertEquals(self._called, 3) return except Exception: self.fail("Wrong exception was raised") self.fail("Exception was not raised")
def testPreferOriginalException(self): """ Test that if an exception is raised both from the with statement and from the finally clause, the one from the with statement is the one that's actually raised. More info in: http://docs.python.org/ 2.6/library/stdtypes.html#contextmanager.__exit__ """ try: with utils.RollbackContext() as rollback: rollback.prependDefer(self._raiseDef, self.UndoException()) raise self.OriginalException() except self.OriginalException: return except self.UndoException: self.fail("Wrong exception was raised - from undo function. \ should have re-raised OriginalException") except Exception: self.fail("Wrong exception was raised") self.fail("Exception was not raised")
def releaseResource(self, namespace, name): # WARN : unlike in resource acquire the user now has the request # object and can CANCEL THE REQUEST at any time. Always use # request.grant between try and except to properly handle such # a case full_name = "%s.%s" % (namespace, name) self._log.debug("Trying to release resource '%s'", full_name) with utils.RollbackContext() as contextCleanup, self._syncRoot.shared: try: namespaceObj = self._namespaces[namespace] except KeyError: raise ValueError( "Namespace '%s' is not registered with this " "manager", namespace) resources = namespaceObj.resources with namespaceObj.lock: try: resource = resources[name] except KeyError: raise ValueError("Resource '%s.%s' is not currently " "registered" % (namespace, name)) resource.activeUsers -= 1 self._log.debug("Released resource '%s' (%d active users)", full_name, resource.activeUsers) # Is some one else is using the resource if resource.activeUsers > 0: return self._log.debug( "Resource '%s' is free, finding out if anyone " "is waiting for it.", full_name) # Grant a request while True: # Is there someone waiting for the resource if len(resource.queue) == 0: self._freeResource(resources[name]) del resources[name] self._log.debug( "No one is waiting for resource '%s', " "Clearing records.", full_name) return self._log.debug( "Resource '%s' has %d requests in queue. " "Handling top request.", full_name, len(resource.queue)) nextRequest = resource.queue.pop() # We lock the request to simulate a transaction. We cannot # grant the request before there is a resource switch. And # we can't do a resource switch before we can guarantee # that the request will be granted. with nextRequest.syncRoot: if nextRequest.canceled(): self._log.debug( "Request '%s' was canceled, " "Ignoring it.", nextRequest) continue try: self._switchLockType(resource, nextRequest.lockType) except Exception: self._log.warning( "Resource factory failed to create " "resource '%s'. Canceling request.", full_name, exc_info=True) nextRequest.cancel() continue nextRequest.grant() contextCleanup.defer( partial( nextRequest.emit, ResourceRef(namespace, name, resource.realObj, nextRequest.reqID))) resource.activeUsers += 1 self._log.debug("Request '%s' was granted", nextRequest) break # If the lock is exclusive were done if resource.currentLock == EXCLUSIVE: return # Keep granting shared locks self._log.debug("This is a shared lock. Granting all shared " "requests") while len(resource.queue) > 0: nextRequest = resource.queue[-1] if nextRequest.canceled(): resource.queue.pop() continue if nextRequest.lockType == EXCLUSIVE: break nextRequest = resource.queue.pop() try: nextRequest.grant() contextCleanup.defer( partial( nextRequest.emit, ResourceRef(namespace, name, resource.realObj, nextRequest.reqID))) except RequestAlreadyProcessedError: continue resource.activeUsers += 1 self._log.debug( "Request '%s' was granted (%d " "active users)", nextRequest, resource.activeUsers)
def registerResource(self, namespace, name, lockType, callback): """ Register to acquire a resource asynchronously. :returns: a request object that tracks the current request. """ full_name = "%s.%s" % (namespace, name) if not self._resourceNameValidator.match(name): raise se.InvalidResourceName(name) if lockType not in (SHARED, EXCLUSIVE): raise InvalidLockType("Invalid locktype %r was used" % lockType) request = Request(namespace, name, lockType, callback) self._log.debug("Trying to register resource '%s' for lock type '%s'", full_name, lockType) with utils.RollbackContext() as contextCleanup, self._syncRoot.shared: try: namespaceObj = self._namespaces[namespace] except KeyError: raise ValueError("Namespace '%s' is not registered with this " "manager" % namespace) resources = namespaceObj.resources with namespaceObj.lock: try: resource = resources[name] except KeyError: if not namespaceObj.factory.resourceExists(name): raise KeyError("No such resource '%s'" % (full_name)) else: if len(resource.queue) == 0 and \ resource.currentLock == SHARED and \ request.lockType == SHARED: resource.activeUsers += 1 self._log.debug( "Resource '%s' found in shared state " "and queue is empty, Joining current " "shared lock (%d active users)", full_name, resource.activeUsers) request.grant() contextCleanup.defer( request.emit, ResourceRef(namespace, name, resource.realObj, request.reqID)) return RequestRef(request) resource.queue.insert(0, request) self._log.debug( "Resource '%s' is currently locked, " "Entering queue (%d in queue)", full_name, len(resource.queue)) return RequestRef(request) # TODO : Creating the object inside the namespace lock causes # the entire namespace to lock and might cause # performance issues. As this is no currently a problem # I left it as it is to keep the code simple. If there # is a bottleneck in the resource framework, its # probably here. try: obj = namespaceObj.factory.createResource(name, lockType) except: self._log.warning( "Resource factory failed to create resource" " '%s'. Canceling request.", full_name, exc_info=True) contextCleanup.defer(request.cancel) return RequestRef(request) resource = resources[name] = ResourceInfo(obj, namespace, name) resource.currentLock = request.lockType resource.activeUsers += 1 self._log.debug( "Resource '%s' is free. Now locking as '%s' " "(1 active user)", full_name, request.lockType) request.grant() contextCleanup.defer( request.emit, ResourceRef(namespace, name, resource.realObj, request.reqID)) return RequestRef(request)
def test(self): with utils.RollbackContext() as rollback: rollback.prependDefer(self._callDef) self.assertEquals(self._called, 1)