Source code for connector.components.locker
# Copyright 2018 Camptocamp SA
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
import logging
import psycopg2
from odoo.addons.component.core import Component
from odoo.addons.connector.exception import RetryableJobError # pylint: disable=W7950
_logger = logging.getLogger(__name__)
[docs]class RecordLocker(Component):
"""Component allowing to lock record(s) for the current transaction
Example of usage::
self.component('record.locker').lock(self.records)
See the definition of :meth:`~lock` for details.
"""
_name = "base.record.locker"
_inherit = ["base.connector"]
_usage = "record.locker"
[docs] def lock(self, records, seconds=None, ignore_retry=True):
"""Lock the records.
Lock the record so we are sure that only one job is running for this
record(s) if concurrent jobs have to run a job for the same record(s).
When concurrent jobs try to work on the same record(s), the first one
will lock and proceed, the others will fail to acquire it and will be
retried later
(:exc:`~odoo.addons.queue_job.exception.RetryableJobError` is raised).
The lock is using a ``FOR UPDATE NOWAIT`` so any concurrent transaction
trying FOR UPDATE/UPDATE will be rejected until the current transaction
is committed or rollbacked.
A classical use case for this is to prevent concurrent exports.
The following parameters are forwarded to the exception
:exc:`~odoo.addons.queue_job.exception.RetryableJobError`
:param seconds: In case of retry because the lock cannot be acquired,
in how many seconds it must be retried. If not set,
the queue_job configuration is used.
:param ignore_retry: If True, the retry counter of the job will not be
increased.
"""
sql = ("SELECT id FROM %s WHERE ID IN %%s FOR UPDATE NOWAIT" %
self.model._table)
try:
self.env.cr.execute(sql, (tuple(records.ids),),
log_exceptions=False)
except psycopg2.OperationalError:
_logger.info(
"A concurrent job is already working on the same "
"record (%s with one id in %s). Job delayed later.",
self.model._name,
tuple(records.ids),
)
raise RetryableJobError(
"A concurrent job is already working on the same record "
"(%s with one id in %s). The job will be retried later."
% (self.model._name, tuple(records.ids),),
seconds=seconds, ignore_retry=ignore_retry
)