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 
raw_fcntl_lock(int fd,int rw,off_t off,off_t len,bool waitflag)66 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 	fl.l_type = rw;
71 	fl.l_whence = SEEK_SET;
72 	fl.l_start = off;
73 	fl.l_len = len;
74 	fl.l_pid = 0;
75 
76 	cmd = waitflag ? F_SETLKW : F_SETLK;
77 
78 	return fcntl(fd, cmd, &fl);
79 }
80 
raw_fcntl_unlock(int fd,off_t off,off_t len)81 static int raw_fcntl_unlock(int fd, off_t off, off_t len)
82 {
83 	struct flock fl;
84 	fl.l_type = F_UNLCK;
85 	fl.l_whence = SEEK_SET;
86 	fl.l_start = off;
87 	fl.l_len = len;
88 	fl.l_pid = 0;
89 
90 	return fcntl(fd, F_SETLKW, &fl);
91 }
92 
93 
94 int pipe_r;
95 int pipe_w;
96 char buf[2];
97 
expect_char(char c)98 static void expect_char(char c)
99 {
100 	read(pipe_r, buf, 1);
101 	if (*buf != c) {
102 		fail("We were expecting %c, but got %c", c, buf[0]);
103 	}
104 }
105 
send_char(char c)106 static void send_char(char c)
107 {
108 	write(pipe_w, &c, 1);
109 }
110 
111 
main(int argc,char * argv[])112 int main(int argc, char *argv[])
113 {
114 	int process;
115 	int fd;
116 	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 	pipe(pipes_1_2);
123 	pipe(pipes_2_1);
124 	fd = open(filename, O_RDWR | O_CREAT, 0755);
125 
126 	pid = fork();
127 	if (pid == 0) {
128 		pipe_r = pipes_1_2[0];
129 		pipe_w = pipes_2_1[1];
130 		process = 2;
131 		alarm(15);
132 	} else {
133 		pipe_r = pipes_2_1[0];
134 		pipe_w = pipes_1_2[1];
135 		process = 1;
136 		alarm(15);
137 	}
138 
139 	/* a: process1: F_RDLCK for ofs=0 len=2 */
140 	if (process == 1) {
141 		ret = raw_fcntl_lock(fd, F_RDLCK, 0, 2, true);
142 		ok(ret == 0,
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");
146 		send_char('a');
147 	}
148 
149 	/* process2: F_RDLCK for ofs=0 len=1 */
150 	if (process == 2) {
151 		expect_char('a');
152 		ret = raw_fcntl_lock(fd, F_RDLCK, 0, 1, true);
153 		ok(ret == 0,
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");
157 		send_char('b');
158 	}
159 
160 	/* process1: upgrade ofs=0 len=2 to F_RWLCK (in blocking mode) */
161 	if (process == 1) {
162 		expect_char('b');
163 		send_char('c');
164 		diag("process 1 starts upgrade on range 0,2");
165 		ret = raw_fcntl_lock(fd, F_WRLCK, 0, 2, true);
166 		ok(ret == 0,
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
171 		   read lock */
172 	}
173 
174 	/*
175 	 * process2: F_RDLCK for ofs=1 len=1
176 	 * process2: unlock ofs=0 len=2
177 	 */
178 	if (process == 2) {
179 		expect_char('c'); /* we know process 1 is *about* to lock */
180 		sleep(1);
181 		ret = raw_fcntl_lock(fd, F_RDLCK, 1, 1, true);
182 		ok(ret == 0,
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);
187 		ok(ret == 0,
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");
191 		sleep(1);
192 		send_char('d');
193 	}
194 
195 	if (process == 1) {
196 		expect_char('d');
197 	}
198 
199 	diag("process %d has got to the end\n", process);
200 
201 	return 0;
202 }
203