LCOV - code coverage report
Current view: top level - lib/tdb/test - run-fcntl-deadlock.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 70 71 98.6 %
Date: 2024-04-21 15:09:00 Functions: 5 5 100.0 %

          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             : }

Generated by: LCOV version 1.14