bug-hurd
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Atomic file locking update


From: Samuel Thibault
Subject: Atomic file locking update
Date: Sun, 23 Nov 2014 17:38:32 +0100
User-agent: Mutt/1.5.21+34 (58baf7c9f32f) (2010-12-30)

Hello,

While Svante is working on the record locking, I have worked on at
least fixing whole file locking: there was one bug in our current
implementation: flock(LOCK_SH); flock(LOCK_EX);, as per POSIX, does not
guarantee an atomic upgrade from LOCK_SH to LOCK_EX. But
fcntl(SETLK,F_RDLCK); fcntl(SETLK,F_WRLCK); is supposed to guarantee an
atomic upgrade from F_RDLCK to F_WRLCK.

I have thus added a __LOCK_ATOMIC flag, to be used along LOCK_SH
and LOCK_EX, to guarantee atomic upgrades and downgrades, and then
implemented the support in libfshelp. To avoid starvation, my patch
makes the thread which wants to upgrade to LOCK_EX set the lock type
to LOCK_SH|LOCK_EX, and others manage that, to prevent newer LOCK_SH
lockers.

I have successfully tested it with tdb's tdbtorture up to 5 processes
for now, and it works fine, but perhaps somebody could proofread the
changes again, to make sure we aren't forgetting any scenario?

Samuel

diff --git a/libfshelp/lock-acquire.c b/libfshelp/lock-acquire.c
index 574bc5c..dceee5c 100644
--- a/libfshelp/lock-acquire.c
+++ b/libfshelp/lock-acquire.c
@@ -23,17 +23,30 @@ the Free Software Foundation, 675 Mass Ave, Cambridge, MA 
02139, USA.  */
 
 #define EWOULDBLOCK EAGAIN /* XXX */
 
+/* Remove this when glibc has it.  */
+#ifndef __LOCK_ATOMIC
+#define __LOCK_ATOMIC 16
+#endif
+
 error_t
 fshelp_acquire_lock (struct lock_box *box, int *user, pthread_mutex_t *mut,
                     int flags)
 {
+  int atomic = 0;
+
   if (!(flags & (LOCK_UN | LOCK_EX | LOCK_SH)))
     return 0;
   
   if ((flags & LOCK_UN)
       && (flags & (LOCK_SH | LOCK_EX)))
     return EINVAL;
-  
+
+  if (flags & __LOCK_ATOMIC)
+    {
+      atomic = 1;
+      flags &= ~__LOCK_ATOMIC;
+    }
+
   if (flags & LOCK_EX)
     flags &= ~LOCK_SH;
   
@@ -44,8 +57,10 @@ fshelp_acquire_lock (struct lock_box *box, int *user, 
pthread_mutex_t *mut,
       if (*user & LOCK_UN)
        return 0;
 
-      assert (*user == box->type);
-      assert (*user == LOCK_SH || *user == LOCK_EX);
+      assert (*user == box->type ||
+             (*user == LOCK_SH && box->type == (LOCK_SH | LOCK_EX)));
+      assert (*user == LOCK_SH || *user == LOCK_EX ||
+             *user == (LOCK_SH | LOCK_EX));
 
       if (*user == LOCK_SH)
        {
@@ -60,12 +75,39 @@ fshelp_acquire_lock (struct lock_box *box, int *user, 
pthread_mutex_t *mut,
          box->waiting = 0;
          pthread_cond_broadcast (&box->wait);
        }
+      if (box->type == (LOCK_SH | LOCK_EX) && box->shcount == 1 && 
box->waiting)
+       {
+         box->waiting = 0;
+         pthread_cond_broadcast (&box->wait);
+       }
       *user = LOCK_UN;
     }
   else
     {
+      if (atomic && *user == (flags & (LOCK_SH | LOCK_EX)))
+       /* Already have it the right way. */
+       return 0;
+
+      if (atomic && *user == LOCK_EX && flags & LOCK_SH)
+       {
+         /* Easy atomic change. */
+         *user = LOCK_SH;
+         box->type = LOCK_SH;
+         box->shcount = 1;
+         if (box->waiting)
+           {
+             box->waiting = 0;
+             pthread_cond_broadcast (&box->wait);
+           }
+         return 0;
+       }
+
+      /* We can not have two people upgrading their lock, this is a deadlock! 
*/
+      if (*user == LOCK_SH && atomic && box->type == (LOCK_SH | LOCK_EX))
+       return EDEADLK;
+
       /* If we have an exclusive lock, release it. */
-      if (*user == LOCK_EX)
+      if (*user == LOCK_EX && !atomic)
        {
          *user = LOCK_UN;
          box->type = LOCK_UN;
@@ -75,19 +117,9 @@ fshelp_acquire_lock (struct lock_box *box, int *user, 
pthread_mutex_t *mut,
              pthread_cond_broadcast (&box->wait);
            }
        }
-      
-      /* If there is an exclusive lock, wait for it to end. */
-      while (box->type == LOCK_EX)
-       {
-         if (flags & LOCK_NB)
-           return EWOULDBLOCK;
-         box->waiting = 1;
-         if (pthread_hurd_cond_wait_np (&box->wait, mut))
-           return EINTR;
-       }
 
       /* If we have a shared lock, release it. */
-      if (*user == LOCK_SH)
+      if (*user == LOCK_SH && !atomic)
        {
          *user = LOCK_UN;
          if (!--box->shcount)
@@ -99,12 +131,29 @@ fshelp_acquire_lock (struct lock_box *box, int *user, 
pthread_mutex_t *mut,
                  pthread_cond_broadcast (&box->wait);
                }
            }
+         if (box->type == (LOCK_SH | LOCK_EX) && box->shcount == 1 &&
+             box->waiting)
+           {
+             box->waiting = 0;
+             pthread_cond_broadcast (&box->wait);
+           }
        }
-      
+
+      /* If there is another exclusive lock or a pending upgrade, wait for it 
to
+         end. */
+      while (box->type & LOCK_EX)
+       {
+         if (flags & LOCK_NB)
+           return EWOULDBLOCK;
+         box->waiting = 1;
+         if (pthread_hurd_cond_wait_np (&box->wait, mut))
+           return EINTR;
+       }
+
       assert ((flags & LOCK_SH) || (flags & LOCK_EX));
       if (flags & LOCK_SH)
        {
-         assert (box->type != LOCK_EX);
+         assert (!(box->type & LOCK_EX));
          *user = LOCK_SH;
          box->type = LOCK_SH;
          box->shcount++;
@@ -112,17 +161,27 @@ fshelp_acquire_lock (struct lock_box *box, int *user, 
pthread_mutex_t *mut,
       else if (flags & LOCK_EX)
        {
          /* Wait for any shared (and exclusive) locks to finish. */
-         while (box->type != LOCK_UN)
+         while ((*user == LOCK_SH && box->shcount > 1) ||
+                (*user == LOCK_UN && box->type != LOCK_UN))
            {
              if (flags & LOCK_NB)
                return EWOULDBLOCK;
              else
                {
+                 /* Tell others that we are upgrading.  */
+                 if (*user == LOCK_SH && atomic)
+                   box->type = LOCK_SH | LOCK_EX;
+
                  box->waiting = 1;
                  if (pthread_hurd_cond_wait_np (&box->wait, mut))
                    return EINTR;
                }
            }
+         if (*user == LOCK_SH)
+           {
+             assert (box->shcount == 1);
+             box->shcount = 0;
+           }
          box->type = LOCK_EX;
          *user = LOCK_EX;
        }



reply via email to

[Prev in Thread] Current Thread [Next in Thread]