ubik: add interface for reading during write locks
[openafs.git] / src / ubik / lock.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 #include <afsconfig.h>
11 #include <afs/param.h>
12
13
14 #include <sys/types.h>
15 #include <stdarg.h>
16 #include <errno.h>
17
18 #ifndef AFS_NT40_ENV
19 #include <sys/file.h>
20 #endif
21
22 #include <lock.h>
23 #include <rx/xdr.h>
24
25 #define UBIK_INTERNALS 1
26 #include "ubik.h"
27 #include "ubik_int.h"
28
29 /*! \file
30  * Locks hang off of each transaction, with all the transaction hanging off of
31  * the appropriate dbase.  This package expects to be used in a two-phase locking
32  * protocol, so it doesn't provide a way to release anything but all of the locks in the
33  * transaction.
34  *
35  * At present, it doesn't support the setting of more than one byte-position lock at a time, that is
36  * the length field must be 1.  This doesn't mean that a single transaction can't set more than
37  * one lock, however.
38  *
39  * It is the responsibility of the user to avoid deadlock by setting locks in a partial order.
40  *
41  * #EWOULDBLOCK has been replaced in this file by #EAGAIN. Many Unix's but not
42  * all (eg. HP) do not replace #EWOULDBLOCK with #EAGAIN. The bad news is this
43  * goes over the wire. The good news is that the code path is never triggered
44  * as it requires ulock_getLock to be called with await = 0. And ulock_SetLock
45  * isn't even used in this code base. Since NT doesn't have a native
46  * #EAGAIN, we are replacing all instances of #EWOULDBLOCK with #EAGAIN.
47  * 
48  */
49
50 #define WouldReadBlock(lock)\
51   ((((lock)->excl_locked & WRITE_LOCK) || (lock)->wait_states) ? 0 : 1)
52 #define WouldWriteBlock(lock)\
53   ((((lock)->excl_locked & WRITE_LOCK) || (lock)->readers_reading) ? 0 : 1)
54
55 struct Lock rwlock;
56 int rwlockinit = 1;
57
58 /*!
59  * \brief Set a transaction lock.
60  * \param atype is #LOCKREAD or #LOCKWRITE.
61  * \param await is TRUE if you want to wait for the lock instead of returning
62  * #EWOULDBLOCK.
63  *
64  * \note The #DBHOLD lock must be held.
65  */
66 extern int
67 ulock_getLock(struct ubik_trans *atrans, int atype, int await)
68 {
69     struct ubik_dbase *dbase = atrans->dbase;
70
71     /* On first pass, initialize the lock */
72     if (rwlockinit) {
73         Lock_Init(&rwlock);
74         rwlockinit = 0;
75     }
76
77     if ((atype != LOCKREAD) && (atype != LOCKWRITE))
78         return EINVAL;
79
80     if (atrans->flags & TRDONE)
81         return UDONE;
82
83     if (atype != LOCKREAD && (atrans->flags & TRREADWRITE)) {
84         return EINVAL;
85     }
86
87     if (atrans->locktype != 0) {
88         ubik_print("Ubik: Internal Error: attempted to take lock twice\n");
89         abort();
90     }
91
92 /*
93  *ubik_print("Ubik: DEBUG: Thread 0x%x request %s lock\n", lwp_cpptr,
94  *           ((atype == LOCKREAD) ? "READ" : "WRITE"));
95  */
96
97     /* Check if the lock would would block */
98     if (!await && !(atrans->flags & TRREADWRITE)) {
99         if (atype == LOCKREAD) {
100             if (WouldReadBlock(&rwlock))
101                 return EAGAIN;
102         } else {
103             if (WouldWriteBlock(&rwlock))
104                 return EAGAIN;
105         }
106     }
107
108     /* Create new lock record and add to spec'd transaction:
109      * #if defined(UBIK_PAUSE)
110      * * locktype.  Before doing that, set TRSETLOCK,
111      * * to tell udisk_end that another thread (us) is waiting.
112      * #else
113      * * locktype. This field also tells us if the thread is 
114      * * waiting for a lock: It will be equal to LOCKWAIT.
115      * #endif 
116      */
117 #if defined(UBIK_PAUSE)
118     if (atrans->flags & TRSETLOCK) {
119         printf("Ubik: Internal Error: TRSETLOCK already set?\n");
120         return EBUSY;
121     }
122     atrans->flags |= TRSETLOCK;
123 #else
124     atrans->locktype = LOCKWAIT;
125 #endif /* UBIK_PAUSE */
126     DBRELE(dbase);
127     if (atrans->flags & TRREADWRITE) {
128         /* noop; don't actually lock anything for TRREADWRITE */
129     } else if (atype == LOCKREAD) {
130         ObtainReadLock(&rwlock);
131     } else {
132         ObtainWriteLock(&rwlock);
133     }
134     DBHOLD(dbase);
135     atrans->locktype = atype;
136 #if defined(UBIK_PAUSE)
137     atrans->flags &= ~TRSETLOCK;
138 #if 0
139     /* We don't do this here, because this can only happen in SDISK_Lock,
140      *  and there's already code there to catch this condition.
141      */
142     if (atrans->flags & TRSTALE) {
143         udisk_end(atrans);
144         return UINTERNAL;
145     }
146 #endif
147 #endif /* UBIK_PAUSE */
148
149 /*
150  *ubik_print("Ubik: DEBUG: Thread 0x%x took %s lock\n", lwp_cpptr,
151  *           ((atype == LOCKREAD) ? "READ" : "WRITE"));
152  */
153     return 0;
154 }
155
156 /*!
157  * \brief Release the transaction lock.
158  */
159 void
160 ulock_relLock(struct ubik_trans *atrans)
161 {
162     if (rwlockinit)
163         return;
164
165     if (atrans->locktype == LOCKWRITE && (atrans->flags & TRREADWRITE)) {
166         ubik_print("Ubik: Internal Error: unlocking write lock with "
167                    "TRREADWRITE?\n");
168         abort();
169     }
170
171     if (atrans->flags & TRREADWRITE) {
172         /* noop, TRREADWRITE means we don't actually lock anything */
173     } else if (atrans->locktype == LOCKREAD) {
174         ReleaseReadLock(&rwlock);
175     } else if (atrans->locktype == LOCKWRITE) {
176         ReleaseWriteLock(&rwlock);
177     }
178
179 /*
180  *ubik_print("Ubik: DEBUG: Thread 0x%x %s unlock\n", lwp_cpptr,
181  *           ((atrans->locktype == LOCKREAD) ? "READ" : "WRITE"));
182  */
183
184     atrans->locktype = 0;
185     return;
186 }
187
188 /*!
189  * \brief debugging hooks
190  */
191 void
192 ulock_Debug(struct ubik_debug *aparm)
193 {
194     if (rwlockinit) {
195         aparm->anyReadLocks = 0;
196         aparm->anyWriteLocks = 0;
197     } else {
198         aparm->anyReadLocks = rwlock.readers_reading;
199         aparm->anyWriteLocks = ((rwlock.excl_locked == WRITE_LOCK) ? 1 : 0);
200     }
201 }