Line data Source code
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"
13 : #include "replace.h"
14 : #include "system/filesys.h"
15 : #include "system/time.h"
16 : #include <errno.h>
17 : #include "tap-interface.h"
18 :
19 : /*
20 : * This tests the low level locking requirement
21 : * for the allrecord lock/prepare_commit and traverse_read interaction.
22 : *
23 : * The pattern with the traverse_read and prepare_commit interaction is
24 : * the following:
25 : *
26 : * 1. transaction_start got the allrecord lock with F_RDLCK.
27 : *
28 : * 2. the traverse_read code walks the database in a sequence like this
29 : * (per chain):
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
43 : *
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.
46 : *
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.
53 : *
54 : * I think the first step is a standalone test that does this:
55 : *
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
62 : *
63 : * Such a test follows here...
64 : */
65 :
66 4 : static int raw_fcntl_lock(int fd, int rw, off_t off, off_t len, bool waitflag)
67 : {
68 : struct flock fl;
69 : int cmd;
70 4 : fl.l_type = rw;
71 4 : fl.l_whence = SEEK_SET;
72 4 : fl.l_start = off;
73 4 : fl.l_len = len;
74 4 : fl.l_pid = 0;
75 :
76 4 : cmd = waitflag ? F_SETLKW : F_SETLK;
77 :
78 4 : return fcntl(fd, cmd, &fl);
79 : }
80 :
81 1 : static int raw_fcntl_unlock(int fd, off_t off, off_t len)
82 : {
83 : struct flock fl;
84 1 : fl.l_type = F_UNLCK;
85 1 : fl.l_whence = SEEK_SET;
86 1 : fl.l_start = off;
87 1 : fl.l_len = len;
88 1 : fl.l_pid = 0;
89 :
90 1 : return fcntl(fd, F_SETLKW, &fl);
91 : }
92 :
93 :
94 : int pipe_r;
95 : int pipe_w;
96 : char buf[2];
97 :
98 4 : static void expect_char(char c)
99 : {
100 4 : read(pipe_r, buf, 1);
101 4 : if (*buf != c) {
102 0 : fail("We were expecting %c, but got %c", c, buf[0]);
103 : }
104 4 : }
105 :
106 4 : static void send_char(char c)
107 : {
108 4 : write(pipe_w, &c, 1);
109 4 : }
110 :
111 :
112 1 : int main(int argc, char *argv[])
113 : {
114 : int process;
115 : int fd;
116 1 : const char *filename = "run-fcntl-deadlock.lck";
117 : int pid;
118 : int pipes_1_2[2];
119 : int pipes_2_1[2];
120 : int ret;
121 :
122 1 : pipe(pipes_1_2);
123 1 : pipe(pipes_2_1);
124 1 : fd = open(filename, O_RDWR | O_CREAT, 0755);
125 :
126 1 : pid = fork();
127 2 : if (pid == 0) {
128 1 : pipe_r = pipes_1_2[0];
129 1 : pipe_w = pipes_2_1[1];
130 1 : process = 2;
131 1 : alarm(15);
132 : } else {
133 1 : pipe_r = pipes_2_1[0];
134 1 : pipe_w = pipes_1_2[1];
135 1 : process = 1;
136 1 : alarm(15);
137 : }
138 :
139 : /* a: process1: F_RDLCK for ofs=0 len=2 */
140 2 : if (process == 1) {
141 1 : ret = raw_fcntl_lock(fd, F_RDLCK, 0, 2, true);
142 1 : ok(ret == 0,
143 : "process 1 lock ofs=0 len=2: %d - %s",
144 : ret, strerror(errno));
145 1 : diag("process 1 took read lock on range 0,2");
146 1 : send_char('a');
147 : }
148 :
149 : /* process2: F_RDLCK for ofs=0 len=1 */
150 2 : if (process == 2) {
151 1 : expect_char('a');
152 1 : ret = raw_fcntl_lock(fd, F_RDLCK, 0, 1, true);
153 1 : ok(ret == 0,
154 : "process 2 lock ofs=0 len=1: %d - %s",
155 : ret, strerror(errno));;
156 1 : diag("process 2 took read lock on range 0,1");
157 1 : send_char('b');
158 : }
159 :
160 : /* process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode) */
161 2 : if (process == 1) {
162 1 : expect_char('b');
163 1 : send_char('c');
164 1 : diag("process 1 starts upgrade on range 0,2");
165 1 : ret = raw_fcntl_lock(fd, F_WRLCK, 0, 2, true);
166 1 : ok(ret == 0,
167 : "process 1 RW lock ofs=0 len=2: %d - %s",
168 : ret, strerror(errno));
169 1 : diag("process 1 got read upgrade done");
170 : /* at this point process 1 is blocked on 2 releasing the
171 : read lock */
172 : }
173 :
174 : /*
175 : * process2: F_RDLCK for ofs=1 len=1
176 : * process2: unlock ofs=0 len=2
177 : */
178 2 : if (process == 2) {
179 1 : expect_char('c'); /* we know process 1 is *about* to lock */
180 1 : sleep(1);
181 1 : ret = raw_fcntl_lock(fd, F_RDLCK, 1, 1, true);
182 1 : ok(ret == 0,
183 : "process 2 lock ofs=1 len=1: %d - %s",
184 : ret, strerror(errno));
185 1 : diag("process 2 got read lock on 1,1\n");
186 1 : ret = raw_fcntl_unlock(fd, 0, 2);
187 1 : ok(ret == 0,
188 : "process 2 unlock ofs=0 len=2: %d - %s",
189 : ret, strerror(errno));
190 1 : diag("process 2 released read lock on 0,2\n");
191 1 : sleep(1);
192 1 : send_char('d');
193 : }
194 :
195 2 : if (process == 1) {
196 1 : expect_char('d');
197 : }
198 :
199 2 : diag("process %d has got to the end\n", process);
200 :
201 2 : return 0;
202 : }
|