80 lines
2.9 KiB
Python
80 lines
2.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2013-2017 Camptocamp SA
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
|
|
|
|
import hashlib
|
|
import logging
|
|
import struct
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def pg_try_advisory_lock(env, lock):
|
|
""" Try to acquire a Postgres transactional advisory lock.
|
|
|
|
The function tries to acquire a lock, returns a boolean indicating
|
|
if it could be obtained or not. An acquired lock is released at the
|
|
end of the transaction.
|
|
|
|
A typical use is to acquire a lock at the beginning of an importer
|
|
to prevent 2 jobs to do the same import at the same time. Since the
|
|
record doesn't exist yet, we can't put a lock on a record, so we put
|
|
an advisory lock.
|
|
|
|
Example:
|
|
- Job 1 imports Partner A
|
|
- Job 2 imports Partner B
|
|
- Partner A has a category X which happens not to exist yet
|
|
- Partner B has a category X which happens not to exist yet
|
|
- Job 1 import category X as a dependency
|
|
- Job 2 import category X as a dependency
|
|
|
|
Since both jobs are executed concurrently, they both create a record
|
|
for category X so we have duplicated records. With this lock:
|
|
|
|
- Job 1 imports Partner A, it acquires a lock for this partner
|
|
- Job 2 imports Partner B, it acquires a lock for this partner
|
|
- Partner A has a category X which happens not to exist yet
|
|
- Partner B has a category X which happens not to exist yet
|
|
- Job 1 import category X as a dependency, it acquires a lock for
|
|
this category
|
|
- Job 2 import category X as a dependency, try to acquire a lock
|
|
but can't, Job 2 is retried later, and when it is retried, it
|
|
sees the category X created by Job 1.
|
|
|
|
The lock is acquired until the end of the transaction.
|
|
|
|
Usage example:
|
|
|
|
::
|
|
|
|
lock_name = 'import_record({}, {}, {}, {})'.format(
|
|
self.backend_record._name,
|
|
self.backend_record.id,
|
|
self.model._name,
|
|
self.external_id,
|
|
)
|
|
if pg_try_advisory_lock(lock_name):
|
|
# do sync
|
|
else:
|
|
raise RetryableJobError('Could not acquire advisory lock',
|
|
seconds=2,
|
|
ignore_retry=True)
|
|
|
|
:param env: the Odoo Environment
|
|
:param lock: The lock name. Can be anything convertible to a
|
|
string. It needs to represents what should not be synchronized
|
|
concurrently so usually the string will contain at least: the
|
|
action, the backend type, the backend id, the model name, the
|
|
external id
|
|
:return True/False whether lock was acquired.
|
|
"""
|
|
hasher = hashlib.sha1(str(lock).encode())
|
|
# pg_lock accepts an int8 so we build an hash composed with
|
|
# contextual information and we throw away some bits
|
|
int_lock = struct.unpack('q', hasher.digest()[:8])
|
|
|
|
env.cr.execute('SELECT pg_try_advisory_xact_lock(%s);', (int_lock,))
|
|
acquired = env.cr.fetchone()[0]
|
|
return acquired
|