Model Locking Mixin & Decorator (MySQL Advisory Locks)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
from django.db import connection

class LockableObject(object):

    default_timeout = 45

    def __init__(self, *args, **kwargs):
        
        super(LockableObject, self).__init__(*args, **kwargs)
        
        self.dbcursor = connection.cursor()
        self.lock_id = None
        
    def get_lock_name(self):
        return '%s|%s' % (self.__class__.__name__,
                          self.lock_id)
    
    def lock(self):

        if hasattr(self, 'id'):
            self.lock_id = self.id
        else:
            self.lock_id = 0
            
        lock_name = self.get_lock_name()
        
        self.dbcursor.execute('select get_lock("%s",%s) as lock_success' % (lock_name,
                                                                            self.default_timeout))

        success = ( self.dbcursor.fetchone()[0] == 1 )
        
        if not success:
            raise EnvironmentError, 'Acquiring lock "%s" timed out after %d seconds' % (lock_name, self.default_timeout)
        
        return success
    
    def unlock(self):
        self.dbcursor.execute('select release_lock("%s")' % self.get_lock_name())
        

def require_object_lock(func):
    
    def wrapped(*args, **kwargs):

        lock_object = args[0]
        
        lock_object.lock()

        try:
            return func(*args, **kwargs)
        finally:
            lock_object.unlock()
        
    return wrapped



##########################################################################
# Example

from django.db import models
from django.core.mail import mail_admins

class Notification(models.Model, LockableObject):
    message = models.CharField()
    sent = models.BooleanField()

    @require_object_lock
    def send(self):
        if not self.sent:
            mail_admins('Notification',
                        self.message)
            self.sent = True
            self.save()

a = Notification(message='Hello world',
                 sent=False)

# Important to save; we can't lock a specific object without it having
# an 'id' attribute.
a.save()

a.send()

# Now, we are guaranteed that no matter how many threads try, and no
# matter their timing, calls to the send() method of this row's object
# can only generate ONE mail_admins() call.  We've prevented the race
# condition of two threads calling send() at the same time and both of
# them seeing self.sent as False and sending the mail twice.
#
# Note that if mail_admins failed and threw an exception, the
# require_object_lock decorator catches the exception, releases the
# lock, then raises it to let it fulfill its normal behavior.

More like this

  1. Django and Twill by spookylukey 6 years, 1 month ago
  2. models.py with django_dag models for parts hierarchy by j_syk 2 years, 8 months ago
  3. Semi-Portable recaptcha integration with django forms by pinkeen 3 years, 4 months ago
  4. Improved Pickled Object Field by taavi223 4 years, 8 months ago
  5. FieldsetForm by Ciantic 7 years ago

Comments

(Forgotten your password?)