async def begin(self, read_only: bool = False) -> ITransaction: """Starts a new transaction. """ # already has txn registered, as long as connection is closed, it # is safe txn: typing.Optional[ITransaction] = task_vars.txn.get() if (txn is not None and txn.manager == self and txn.status in (Status.ABORTED, Status.COMMITTED, Status.CONFLICT)): # re-use txn if possible txn.status = Status.ACTIVE if (txn._db_conn is not None and getattr(txn._db_conn, '_in_use', None) is None): try: await self._close_txn(txn) except Exception: logger.warn('Unable to close spurious connection', exc_info=True) else: txn = Transaction(self, read_only=read_only) try: txn.user = get_authenticated_user_id() except RequestNotFound: pass await txn.tpc_begin() # make sure to explicitly set! task_vars.txn.set(txn) return txn
async def _test_pg_txn(postgres, guillotina_main): """Test a low level transaction""" dsn = "postgres://postgres:@localhost:5432/guillotina" partition_object = "guillotina.db.interfaces.IPartition" aps = APgStorage(dsn=dsn, partition=partition_object, name='db') await aps.initialize() conn = await aps.open() obj = Root() writer = IWriter(obj) tm = TransactionManager(DummyStorage()) txn = Transaction(tm) await aps.tpc_begin(txn, conn) await aps.precommit(txn) await aps.store(ROOT_ID, 0, writer, obj, txn) await aps.tpc_vote(txn) await aps.tpc_finish(txn) await aps.close(conn) tm = TransactionManager(DummyStorage()) txn = Transaction(tm) await aps.tpc_begin(txn, conn) result = await aps.load(txn, ROOT_ID) await aps.abort(txn) await aps.close(txn._db_conn) obj2 = reader(result) assert obj.__name__ == obj2.__name__ await cleanup(aps)
async def test_do_not_cache_large_object(guillotina_main, loop): tm = mocks.MockTransactionManager() storage = tm._storage txn = Transaction(tm) cache = BasicCache(txn) txn._cache = cache ob = create_content() ob.foobar = "X" * cache.max_cache_record_size # push size above cache threshold storage.store(None, None, None, ob, txn) loaded = await txn.get(ob.__uuid__) assert id(loaded) != id(ob) assert loaded.__uuid__ == ob.__uuid__
async def test_do_not_cache_large_object(dummy_guillotina): tm = mocks.MockTransactionManager() storage = tm._storage txn = Transaction(tm) cache = MemoryCache(storage, txn) txn._cache = cache ob = create_content() ob.foobar = 'X' * cache.max_cache_record_size # push size above cache threshold storage.store(ob) loaded = await txn.get(ob._p_oid) assert id(loaded) != id(ob) assert loaded._p_oid == ob._p_oid assert len(cache._actions) == 0
async def test_record_transaction_cache_hit_container( self, dummy_guillotina, metrics_registry): storage = AsyncMock() mng = TransactionManager(storage) cache = AsyncMock() cache.get.return_value = { "state": pickle.dumps(create_content(Container)), "zoid": ROOT_ID, "tid": 1, "id": "foobar", } strategy = AsyncMock() txn = Transaction(mng, cache=cache, strategy=strategy) await txn.get("foobar") assert (metrics_registry.get_sample_value("guillotina_cache_ops_total", { "type": "_get", "result": "hit" }) is None) assert (metrics_registry.get_sample_value("guillotina_cache_ops_total", { "type": "_get", "result": "hit_roots" }) == 1.0)
async def test_fill_cache_doesnt_cache_large_objects(guillotina_main, mocked_cache_set): tm = mocks.MockTransactionManager() txn = Transaction(tm) cache = BasicCache(txn) # Mock storing an object that is over the limit obj = Mock() obj.__uuid__ = "foo" obj.__serial__ = "bar" obj.__name__ = "ba" obj.__of__ = "bi" pickled = MagicMock() pickled.__len__.return_value = BasicCache.max_cache_record_size + 10 await cache.store_object(obj, pickled) # Call fill_cache and check that caching was skipped await cache.fill_cache() cache.set.assert_not_called() # Now add smaller object and check that is cached pickled.__len__.return_value = 1 await cache.store_object(obj, pickled) await cache.fill_cache() cache.set.assert_called_once()
async def test_cache_object(dummy_guillotina): tm = mocks.MockTransactionManager() storage = tm._storage txn = Transaction(tm) cache = MemoryCache(storage, txn) txn._cache = cache ob = create_content() storage.store(ob) loaded = await txn.get(ob._p_oid) assert id(loaded) != id(ob) assert loaded._p_oid == ob._p_oid assert cache._actions[0]['action'] == 'stored' # and load from cache await txn.get(ob._p_oid) assert cache._actions[-1]['action'] == 'loaded'
async def begin(self, request=None): """Starts a new transaction. """ self._db_conn = await self._storage.open() if request is None: if self.request is None: self.request = get_current_request() request = self.request user = get_authenticated_user_id(request) if self._txn is not None: if self._pool is None: self._pool = LifoQueue() # Save the actual transaction and start a new one self._pool.put(self._txn) self._txn = txn = Transaction(self, request=request) # CACHE!! if user is not None: txn.user = user await txn.tpc_begin(self._db_conn) return txn
async def test_cache_object(guillotina_main, loop): tm = mocks.MockTransactionManager() storage = tm._storage txn = Transaction(tm) cache = BasicCache(txn) txn._cache = cache ob = create_content() storage.store(None, None, None, ob, txn) loaded = await txn.get(ob.__uuid__) assert id(loaded) != id(ob) assert loaded.__uuid__ == ob.__uuid__ assert cache._hits == 0 assert cache._misses == 1 # and load from cache await txn.get(ob.__uuid__) assert cache._hits == 1
async def begin(self, request=None): """Starts a new transaction. """ if request is None: try: request = get_current_request() except RequestNotFound: pass user = None txn = None # already has txn registered, as long as connection is closed, it # is safe if (getattr(request, '_txn', None) is not None and request._txn.status in (Status.ABORTED, Status.COMMITTED, Status.CONFLICT)): # re-use txn if possible txn = request._txn txn.status = Status.ACTIVE if txn._db_conn is not None: try: await self._close_txn(txn) except Exception: logger.warn('Unable to close spurious connection', exc_info=True) else: txn = Transaction(self, request=request) self._last_txn = txn if request is not None: # register tm and txn with request request._tm = self request._txn = txn user = get_authenticated_user_id(request) if user is not None: txn.user = user db_conn = self._last_db_conn = await self._storage.open() txn._query_count_start = _get_conn_query_count(db_conn) await txn.tpc_begin(db_conn) return txn
async def begin(self, request=None): """Starts a new transaction. """ if request is None: try: request = get_current_request() except RequestNotFound: pass user = None txn = None # already has txn registered, as long as connection is closed, it # is safe if (getattr(request, '_txn', None) is not None and request._txn.status in (Status.ABORTED, Status.COMMITTED, Status.CONFLICT)): # re-use txn if possible txn = request._txn txn.status = Status.ACTIVE if (txn._db_conn is not None and getattr(txn._db_conn, '_in_use', None) is None): try: await self._close_txn(txn) except Exception: logger.warn('Unable to close spurious connection', exc_info=True) else: txn = Transaction(self, request=request) self._last_txn = txn if request is not None: # register tm and txn with request request._tm = self request._txn = txn user = get_authenticated_user_id(request) if user is not None: txn.user = user await txn.tpc_begin() return txn
async def begin(self, request=None): """Starts a new transaction. """ db_conn = self._last_db_conn = await self._storage.open() if request is None: try: request = get_current_request() except RequestNotFound: pass user = None txn = None # already has txn registered, as long as connection is closed, it # is safe if (getattr(request, '_txn', None) is not None and request._txn._db_conn is None and request._txn.status in (Status.ABORTED, Status.COMMITTED)): # re-use txn if possible txn = request._txn txn.status = Status.ACTIVE # XXX do we want to auto clean up here? Or throw an error? # This will break tests that are starting multiple transactions # else: # await self._close_txn(request._txn) else: txn = Transaction(self, request=request) self._last_txn = txn if request is not None: # register tm and txn with request request._tm = self request._txn = txn user = get_authenticated_user_id(request) if user is not None: txn.user = user await txn.tpc_begin(db_conn) return txn
async def test_cache_object_from_child(guillotina_main, loop): tm = mocks.MockTransactionManager() storage = tm._storage txn = Transaction(tm) cache = BasicCache(txn) txn._cache = cache ob = create_content() parent = create_content() ob.__parent__ = parent storage.store(None, None, None, parent, txn) storage.store(None, None, None, ob, txn) loaded = await txn.get_child(parent, ob.id) assert cache._hits == 0 loaded = await txn.get_child(parent, ob.id) assert cache._hits == 1 assert id(loaded) != id(ob) assert loaded.__uuid__ == ob.__uuid__
async def test_cache_object_from_child(dummy_guillotina): tm = mocks.MockTransactionManager() storage = tm._storage txn = Transaction(tm) cache = MemoryCache(storage, txn) txn._cache = cache ob = create_content() parent = create_content() ob.__parent__ = parent storage.store(parent) storage.store(ob) loaded = await txn.get_child(parent, ob.id) assert len(cache._actions) == 1 assert cache._actions[0]['action'] == 'stored' loaded = await txn.get_child(parent, ob.id) assert cache._actions[-1]['action'] == 'loaded' assert id(loaded) != id(ob) assert loaded._p_oid == ob._p_oid
async def _test_read_something(postgres, guillotina_main): """Low level test checks that root is there""" dsn = "postgres://postgres:@localhost:5432/guillotina" partition_object = "guillotina.db.interfaces.IPartition" aps = APgStorage(dsn=dsn, partition=partition_object, name='db') await aps.initialize() conn = await aps.open() tm = TransactionManager(aps) txn = Transaction(tm) await txn.tpc_begin(conn) lasttid = await aps.last_transaction(txn) await aps.load(txn, ROOT_ID) await aps.abort(txn) await aps.remove() await aps.close(txn._db_conn) await cleanup(aps) assert lasttid == 1
async def test_record_transaction_cache_empty(self, dummy_guillotina, metrics_registry): storage = AsyncMock() mng = TransactionManager(storage) cache = AsyncMock() cache.get.return_value = transaction._EMPTY strategy = AsyncMock() txn = Transaction(mng, cache=cache, strategy=strategy) ob = create_content(Container) with pytest.raises(KeyError): await txn.get_annotation(ob, "foobar") assert (metrics_registry.get_sample_value( "guillotina_cache_ops_total", { "type": "_get_annotation", "result": "hit_empty" }) == 1.0)
async def test_no_tid_created_for_reads(dummy_request, loop): tm = mocks.MockTransactionManager() trns = Transaction(tm, loop=loop, read_only=True) await trns.tpc_begin() assert trns._tid is None
async def test_tid_created_for_writes(dummy_request, loop): tm = mocks.MockTransactionManager() trns = Transaction(tm, loop=loop) await trns.tpc_begin() assert trns._tid == 1
async def test_no_tid_created_for_reads(dummy_request, loop): dummy_request._db_write_enabled = False tm = mocks.MockTransactionManager() trns = Transaction(tm, dummy_request, loop=loop) await trns.tpc_begin() assert trns._tid is None