#37000: cursor_iter relies on GC for server-side cursor cleanup, causing
transaction abort after savepoint rollback
-------------------------------------+-------------------------------------
     Reporter:  Ratskó László        |                    Owner:  (none)
         Type:  Bug                  |                   Status:  new
    Component:  Database layer       |                  Version:  4.2
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:  iterator, server-    |             Triage Stage:  Accepted
  side-cursor, savepoint, psycopg3   |
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Comment (by Ratskó László):

 Thanks for accepting the ticket!

 To clarify: yes, this is specifically a nested transaction.atomic() issue
 (savepoints). We have ATOMIC_REQUESTS = True, so every request is already
 in a transaction, and any transaction.atomic() in our code creates a
 savepoint. Sorry for not mentioning that in the report.

 Our pattern follows the documented approach — catching exceptions outside
 the atomic block:


 {{{
 # ATOMIC_REQUESTS = True → request is already in a transaction

 def export_view(request):
     try:
         with transaction.atomic():  # this creates a savepoint (nested)
             for item in queryset.iterator():  # server-side cursor
                 process(item)  # may raise
     except SomeError:
         save_error_status()  # ← fails with InFailedSqlTransaction
 }}}

 This matches the pattern from the docs:



 {{{
 @transaction.atomic
 def viewfunc(request):
     create_parent()

     try:
         with transaction.atomic():
             generate_relationships()
     except IntegrityError:
         handle_exception()

     add_children()
 }}}

 We tested both scenarios:

 Nested (savepoint) — FAILS:

 {{{

   with transaction.atomic():          # outer (ATOMIC_REQUESTS)
       try:
           with transaction.atomic():  # inner → savepoint
               for item in queryset.iterator():
                   raise ValueError()
       except ValueError:
           Model.objects.create(...)    # ← InFailedSqlTransaction
 }}}

 Non-nested (full rollback) — WORKS:


 {{{

   try:
       with transaction.atomic():      # not nested → full transaction
           for item in queryset.iterator():
               raise ValueError()
   except ValueError:
       Model.objects.create(...)        # ← OK, transaction is IDLE after
 full rollback
 }}}

 Let me know if we can help somehow by providing more information. Thanks
 again!
-- 
Ticket URL: <https://code.djangoproject.com/ticket/37000#comment:2>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019d1c306938-e860aa63-0332-4087-bc06-45df7cdee8d3-000000%40eu-central-1.amazonses.com.

Reply via email to