summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libfshelp/lock-acquire.c95
1 files changed, 77 insertions, 18 deletions
diff --git a/libfshelp/lock-acquire.c b/libfshelp/lock-acquire.c
index 574bc5cb..06c93d8c 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;
}