Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed get_or_create method #1404

Merged
merged 3 commits into from May 14, 2024
Merged

Conversation

ipakeev
Copy link
Contributor

@ipakeev ipakeev commented Jun 14, 2023

Description

I encountered an error: with simultaneous identical requests, the server responds with 500 error code.
tortoise-orm 0.19.3

INFO:     'POST /api/token HTTP/1.1' 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/tortoise/models.py", line 1059, in get_or_create
    await cls.select_for_update().filter(**kwargs).using_db(connection).get(),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/tortoise/queryset.py", line 1020, in _execute
    raise DoesNotExist("Object does not exist")
tortoise.exceptions.DoesNotExist: Object does not exist

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/tortoise/models.py", line 1064, in get_or_create
    return await cls.create(using_db=connection, **defaults, **kwargs), True
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/tortoise/backends/asyncpg/client.py", line 86, in _translate_exceptions
    raise IntegrityError(exc)
tortoise.exceptions.IntegrityError: duplicate key value violates unique constraint "user_phone_key"
DETAIL:  Key (phone)=(9998887764) already exists.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/uvicorn/protocols/http/h11_impl.py", line 407, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/fastapi/applications.py", line 270, in __call__
    await super().__call__(scope, receive, send)
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/applications.py", line 124, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in __call__
    raise exc
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in __call__
    await self.app(scope, receive, _send)
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
    raise exc
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
    await self.app(scope, receive, sender)
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 21, in __call__
    raise e
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 18, in __call__
    await self.app(scope, receive, send)
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/routing.py", line 706, in __call__
    await route.handle(scope, receive, send)
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/routing.py", line 276, in handle
    await self.app(scope, receive, send)
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/starlette/routing.py", line 66, in app
    response = await func(request)
               ^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/fastapi/routing.py", line 235, in app
    raw_response = await run_endpoint_function(
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/fastapi/routing.py", line 161, in run_endpoint_function
    return await dependant.call(**values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/work/push-service/push_service/app/token/routes.py", line 13, in create_token
    user, _ = await User.get_or_create(phone=body.phone)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/tortoise/models.py", line 1066, in get_or_create
    return await cls.filter(**kwargs).using_db(connection).get(), False
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/ipakeev/.virtualenvs/push-service/lib/python3.11/site-packages/tortoise/backends/asyncpg/client.py", line 88, in _translate_exceptions
    raise TransactionManagementError(exc)

How Has This Been Tested?

After applying this PR, everything works as expected:

        try:
            return await cls.filter(**kwargs).using_db(db).get(), False
        except DoesNotExist:
            try:
                print("not found")
                async with in_transaction(connection_name=db.connection_name) as connection:
                    return await cls.create(using_db=connection, **defaults, **kwargs), True
            except (IntegrityError, TransactionManagementError) as exc:
                print("integrity error")
                try:
                    return await cls.filter(**kwargs).using_db(db).get(), False
                except Exception:
                    raise exc
not found
not found
not found
integrity error
integrity error
INFO:     'POST /api/token HTTP/1.1' 200 OK
INFO:     'POST /api/token HTTP/1.1' 200 OK
INFO:     'POST /api/token HTTP/1.1' 200 OK
INFO:     'POST /api/token HTTP/1.1' 200 OK
INFO:     'POST /api/token HTTP/1.1' 200 OK

I did't find a way to reproduce this error in autotests.

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added the changelog accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

long2ice
long2ice previously approved these changes Nov 3, 2023
@@ -77,11 +77,3 @@ async def test_nonconcurrent_get_or_create(self):
self.assertEqual(len(una_created), 1)
for una in unas:
self.assertEqual(una[0], unas[0][0])

@test.skipIf(sys.version_info < (3, 7), "aiocontextvars backport not handling this well")
async def test_concurrent_get_or_create(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove this testcase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it fails in the TestConcurrencyTransactioned. But the same test works in the TestConcurrencyIsolated.

Copy link
Member

@abondar abondar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase on actual develop and add changes to changelog

except (IntegrityError, TransactionManagementError) as exc:
try:
return await cls.filter(**kwargs).using_db(db).get(), False
except Exception:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this try-catch seems like debug artefact? Remove it please

Copy link
Contributor Author

@ipakeev ipakeev May 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to re-raise integrity exception instead of DoesNotExist error

)
except DoesNotExist:
try:
async with in_transaction(connection_name=db.connection_name) as connection:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need transaction here for just single create?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this sub-transaction is used, so that in case of integrity error here - it aborts it's transaction, so we should have one here for it to be aborted, otherwise it can abort whole parent transaction that is out of scope of this function, which would lead for unexpected behaviour for user

Let's make similar transaction here, as it was in your previous version

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@ipakeev ipakeev force-pushed the fix_get_or_create branch 2 times, most recently from 42e90ce to 982d95b Compare May 1, 2024 10:58
@ipakeev ipakeev requested a review from abondar May 1, 2024 11:00
@abondar
Copy link
Member

abondar commented May 7, 2024

Please rebase and resolve conflict

@coveralls
Copy link

Pull Request Test Coverage Report for Build 9043068735

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 7 of 10 (70.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage decreased (-0.03%) to 87.905%

Changes Missing Coverage Covered Lines Changed/Added Lines %
tortoise/models.py 7 10 70.0%
Totals Coverage Status
Change from base Build 9028420328: -0.03%
Covered Lines: 5771
Relevant Lines: 6470

💛 - Coveralls

@abondar abondar merged commit 7cb7e72 into tortoise:develop May 14, 2024
2 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants