Browse Source

Remove RowExclusiveLock on exception_rule

The goal of the modified method is to create or remove the relationship
(in the M2m relation tabel) between the tested model (such as
sale_order) and the exception rules. When the ORM writes on
ExceptionRule.sale_ids (using the example of sale_exception), it will
first proceeds with these updates:

* an UPDATE on exception_rule to set the write_date
* INSERT or DELETE on the relation table
* but then, as "write" is called on the exception rule, the ORM will
  trigger the api.depends to recompute all the "main_exception_ids"
  of the records (sales, ...) related to it, leading to an UPDATE
  for each sale order

We end up with RowExclusiveLock on such records:

* All the records of the relation table added / deleted for the current
sale order
* All the records of exception_rule matching the current sale order
* All the records of sale_order related to the exception rules matching
the current sale order

The first one is expected, the next 2 are not. We can remove the lock on
the exception_rule table by removing `_log_access`, however in any case,
the main_exception_ids computed field will continue to lock many sale
orders, effectively preventing 2 sales orders with the same exception
to be confirmed at the same time.

Reversing the write by writing on SaleOrder instead of ExceptionRule
fixes the 2 unexpected locks. It should not result in more queries: the
"to remove" part generates a DELETE on the relation table for the rule
to remove and the "to add" part generates an INSERT for the rule to add,
both will be exactly the same in both cases.

Related to #1642
Replaces #1638
12.0
Guewen Baconnier 5 years ago
parent
commit
43def640a9
  1. 29
      base_exception/models/base_exception.py

29
base_exception/models/base_exception.py

@ -91,6 +91,8 @@ class BaseExceptionMethod(models.AbstractModel):
rules = self.env['exception.rule'].sudo().search(
self._rule_domain())
all_exception_ids = []
rules_to_remove = {}
rules_to_add = {}
for rule in rules:
records_with_exception = self._detect_exceptions(rule)
reverse_field = self._reverse_field()
@ -98,11 +100,32 @@ class BaseExceptionMethod(models.AbstractModel):
commons = main_records & rule[reverse_field]
to_remove = commons - records_with_exception
to_add = records_with_exception - commons
to_remove_list = [(3, x.id, _) for x in to_remove]
to_add_list = [(4, x.id, _) for x in to_add]
rule.write({reverse_field: to_remove_list + to_add_list})
# we expect to always work on the same model type
rules_to_remove.setdefault(
rule.id, main_records.browse()
).update(to_remove)
rules_to_add.setdefault(
rule.id, main_records.browse()
).update(to_add)
if records_with_exception:
all_exception_ids.append(rule.id)
# Cumulate all the records to attach to the rule
# before linking. We don't want to call "rule.write()"
# which would:
# * write on write_date so lock the expection.rule
# * trigger the recomputation of "main_exception_id" on
# all the sale orders related to the rule, locking them all
# and preventing concurrent writes
# Reversing the write by writing on SaleOrder instead of
# ExceptionRule fixes the 2 kinds of unexpected locks.
# It should not result in more queries than writing on ExceptionRule:
# the "to remove" part generates one DELETE per rule on the relation
# table
# and the "to add" part generates one INSERT (with unnest) per rule.
for rule_id, records in rules_to_remove.items():
records.write({'exception_ids': [(3, rule_id,)]})
for rule_id, records in rules_to_add.items():
records.write(({'exception_ids': [(4, rule_id,)]}))
return all_exception_ids
@api.model

Loading…
Cancel
Save