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
 |