#33410: captureOnCommitCallbacks executes callbacks multiple times
-------------------------------------+-------------------------------------
     Reporter:  flaeppe              |                    Owner:  flaeppe
         Type:  Bug                  |                   Status:  assigned
    Component:  Testing framework    |                  Version:  dev
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
  captureOnCommitCallbacks           |  Unreviewed
    Has patch:  1                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by flaeppe:

Old description:

> When recursively adding multiple on commit callbacks,
> `captureOnCommitCallbacks` executes some callbacks multiple times. Below
> is a test case to reproduce the error.
>
> {{{
> #!div style="font-size: 80%"
>   {{{#!python
>     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"),
>             ],
>         )
>   }}}
> }}}

New description:

 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

 {{{
 #!div style="font-size: 80%"
   {{{#!python
     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"),
             ],
         )
   }}}
 }}}

--

-- 
Ticket URL: <https://code.djangoproject.com/ticket/33410#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 on the web visit 
https://groups.google.com/d/msgid/django-updates/065.c17b0cdfd8ddc15479aa2264840ee92a%40djangoproject.com.

Reply via email to