peewee-async icon indicating copy to clipboard operation
peewee-async copied to clipboard

'NoneType' object has no attribute 'cursor'

Open kalombos opened this issue 2 years ago • 1 comments

Looks like there is a race condition in the snippet:

# This is a part of code from AsyncDatabase.cursor_async
try:
    # this line throws the error
    return (await self._async_conn.cursor(conn=conn))
except:
    await self.close_async()
    raise

_async_conn can't be None here by design because it has been checked in connect_async method. I can guess how it happens:

  1. A and B connects are awaited in the line await self.close_async()
  2. A run the coroutine, closes the connection, does _async_conn = None, B is still waiting on the same line
  3. Connect C goes to open a connect, sets a future and waits in the await conn.connect() line
  4. Connect B goes in close_async and waits for the future to complete. Connect D goes to open a connect inside connect_async and waits for the future to complete.
  5. Connect C completes opening, sets _async_conn not None and releases the future.
  6. Connect B receives control, sets _async_conn to None and closes the connection.
  7. Connect D receives control, exits connect_async and receives an error, because _async_conn is None

kalombos avatar Dec 29 '23 13:12 kalombos

related to #114

kalombos avatar Jan 07 '24 13:01 kalombos

Found a way to reproduce the bug. Inside AsyncPostgresqlConnection.cursor function add the lines:

async def cursor(self, conn=None, *args, **kwargs):
    """Get cursor for connection from pool.
    """
    # lines we have added
    import random
    choice = random.randint(1, 5)
    if choice == 5:
       raise Exception("some network error") # network error imitation



    in_transaction = conn is not None
    if not conn:
        conn = await self.acquire()
    cursor = await conn.cursor(*args, **kwargs)
    cursor.release = functools.partial(
        self.release_cursor, cursor,
        in_transaction=in_transaction)
    return cursor

Create a simple http application with select query on some endpoint. Use yandex-tank to create rps load on the application. We have NoneType' object has no attribute 'cursor soon. if write python with manager.atomic() inside the endpoint code we have "cursor already closed error

kalombos avatar Mar 20 '24 14:03 kalombos