1 #include "../common/tdb_private.h"
2 #include "../common/io.c"
3 #include "../common/tdb.c"
4 #include "../common/lock.c"
5 #include "../common/freelist.c"
6 #include "../common/traverse.c"
7 #include "../common/transaction.c"
8 #include "../common/error.c"
9 #include "../common/open.c"
10 #include "../common/check.c"
11 #include "../common/hash.c"
12 #include "../common/mutex.c"
14 #include "system/filesys.h"
15 #include "system/time.h"
17 #include "tap-interface.h"
20 * This tests the low level locking requirement
21 * for the allrecord lock/prepare_commit and traverse_read interaction.
23 * The pattern with the traverse_read and prepare_commit interaction is
26 * 1. transaction_start got the allrecord lock with F_RDLCK.
28 * 2. the traverse_read code walks the database in a sequence like this
30 * 2.1 chainlock(chainX, F_RDLCK)
31 * 2.2 recordlock(chainX.record1, F_RDLCK)
32 * 2.3 chainunlock(chainX, F_RDLCK)
33 * 2.4 callback(chainX.record1)
34 * 2.5 chainlock(chainX, F_RDLCK)
35 * 2.6 recordunlock(chainX.record1, F_RDLCK)
36 * 2.7 recordlock(chainX.record2, F_RDLCK)
37 * 2.8 chainunlock(chainX, F_RDLCK)
38 * 2.9 callback(chainX.record2)
39 * 2.10 chainlock(chainX, F_RDLCK)
40 * 2.11 recordunlock(chainX.record2, F_RDLCK)
41 * 2.12 chainunlock(chainX, F_RDLCK)
42 * 2.13 goto next chain
44 * So it has always one record locked in F_RDLCK mode and tries to
45 * get the 2nd one before it releases the first one.
47 * 3. prepare_commit tries to upgrade the allrecord lock to F_RWLCK
48 * If that happens at the time of 2.4, the operation of
49 * 2.5 may deadlock with the allrecord lock upgrade.
50 * On Linux step 2.5 works in order to make some progress with the
51 * locking, but on solaris it might fail because the kernel
52 * wants to satisfy the 1st lock requester before the 2nd one.
54 * I think the first step is a standalone test that does this:
56 * process1: F_RDLCK for ofs=0 len=2
57 * process2: F_RDLCK for ofs=0 len=1
58 * process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode)
59 * process2: F_RDLCK for ofs=1 len=1
60 * process2: unlock ofs=0 len=2
61 * process1: should continue at that point
63 * Such a test follows here...
66 static int raw_fcntl_lock(int fd, int rw, off_t off, off_t len, bool waitflag)
71 fl.l_whence = SEEK_SET;
76 cmd = waitflag ? F_SETLKW : F_SETLK;
78 return fcntl(fd, cmd, &fl);
81 static int raw_fcntl_unlock(int fd, off_t off, off_t len)
85 fl.l_whence = SEEK_SET;
90 return fcntl(fd, F_SETLKW, &fl);
98 static void expect_char(char c)
100 read(pipe_r, buf, 1);
102 fail("We were expecting %c, but got %c", c, buf[0]);
106 static void send_char(char c)
108 write(pipe_w, &c, 1);
112 int main(int argc, char *argv[])
116 const char *filename = "run-fcntl-deadlock.lck";
124 fd = open(filename, O_RDWR | O_CREAT, 0755);
128 pipe_r = pipes_1_2[0];
129 pipe_w = pipes_2_1[1];
133 pipe_r = pipes_2_1[0];
134 pipe_w = pipes_1_2[1];
139 /* a: process1: F_RDLCK for ofs=0 len=2 */
141 ret = raw_fcntl_lock(fd, F_RDLCK, 0, 2, true);
143 "process 1 lock ofs=0 len=2: %d - %s",
144 ret, strerror(errno));
145 diag("process 1 took read lock on range 0,2");
149 /* process2: F_RDLCK for ofs=0 len=1 */
152 ret = raw_fcntl_lock(fd, F_RDLCK, 0, 1, true);
154 "process 2 lock ofs=0 len=1: %d - %s",
155 ret, strerror(errno));;
156 diag("process 2 took read lock on range 0,1");
160 /* process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode) */
164 diag("process 1 starts upgrade on range 0,2");
165 ret = raw_fcntl_lock(fd, F_WRLCK, 0, 2, true);
167 "process 1 RW lock ofs=0 len=2: %d - %s",
168 ret, strerror(errno));
169 diag("process 1 got read upgrade done");
170 /* at this point process 1 is blocked on 2 releasing the
175 * process2: F_RDLCK for ofs=1 len=1
176 * process2: unlock ofs=0 len=2
179 expect_char('c'); /* we know process 1 is *about* to lock */
181 ret = raw_fcntl_lock(fd, F_RDLCK, 1, 1, true);
183 "process 2 lock ofs=1 len=1: %d - %s",
184 ret, strerror(errno));
185 diag("process 2 got read lock on 1,1\n");
186 ret = raw_fcntl_unlock(fd, 0, 2);
188 "process 2 unlock ofs=0 len=2: %d - %s",
189 ret, strerror(errno));
190 diag("process 2 released read lock on 0,2\n");
199 diag("process %d has got to the end\n", process);