Opened 3 years ago

Last modified 3 years ago

#33410 closed Bug

captureOnCommitCallbacks executes callbacks multiple times — at Version 2

Reported by: Petter Friberg Owned by: Petter Friberg
Component: Testing framework Version: 4.0
Severity: Release blocker Keywords: captureOnCommitCallbacks
Cc: Eugene Morozov, Adam Johnson Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by Petter Friberg)

When recursively adding multiple on commit callbacks, captureOnCommitCallbacks executes some callbacks multiple times. Below is a test case to reproduce the error.

Patch: https://github.com/django/django/pull/15285

  def test_execute_tree(self):
      """
      A visualisation of the callback tree tested. Each node is expected to be visited
      only once. The child count of a node symbolises the amount of on commit
      callbacks.

      root
      └── branch_1
          ├── branch_2
          │   ├── leaf_1
          │   └── leaf_2
          └── leaf_3
      """
      (
          branch_1_call_counter,
          branch_2_call_counter,
          leaf_1_call_counter,
          leaf_2_call_counter,
          leaf_3_call_counter,
      ) = [itertools.count(start=1) for _ in range(5)]

      def leaf_3():
          next(leaf_3_call_counter)

      def leaf_2():
          next(leaf_2_call_counter)

      def leaf_1():
          next(leaf_1_call_counter)

      def branch_2():
          next(branch_2_call_counter)
          transaction.on_commit(leaf_1)
          transaction.on_commit(leaf_2)

      def branch_1():
          next(branch_1_call_counter)
          transaction.on_commit(branch_2)
          transaction.on_commit(leaf_3)

      with self.captureOnCommitCallbacks(execute=True) as callbacks:
          with transaction.atomic():
              transaction.on_commit(branch_1)

      # Every counter shall only have been called once, starting at 1; next value then
      # has to be 2
      self.assertEqual(next(branch_1_call_counter), 2)
      self.assertEqual(next(branch_2_call_counter), 2)
      self.assertEqual(next(leaf_1_call_counter), 2)
      self.assertEqual(next(leaf_2_call_counter), 2)
      self.assertEqual(next(leaf_3_call_counter), 2)
      # Make sure all calls can be seen and the execution order is correct
      self.assertEqual(
          [(id(callback), callback.__name__) for callback in callbacks],
          [
              (id(branch_1), "branch_1"),
              (id(branch_2), "branch_2"),
              (id(leaf_3), "leaf_3"),
              (id(leaf_1), "leaf_1"),
              (id(leaf_2), "leaf_2"),
          ],
      )

Change History (2)

comment:1 by Petter Friberg, 3 years ago

Has patch: set

comment:2 by Petter Friberg, 3 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top