LCOV - code coverage report
Current view: top level - lib/tdb/test - run-marklock-deadlock.c (source / functions) Hit Total Coverage
Test: coverage report for master 2f515e9b Lines: 122 126 96.8 %
Date: 2024-04-21 15:09:00 Functions: 4 4 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 "tap-interface.h"
      14             : #include <stdlib.h>
      15             : #include <sys/types.h>
      16             : #include <sys/wait.h>
      17             : #include <stdarg.h>
      18             : #include "logging.h"
      19             : 
      20             : static TDB_DATA key, data;
      21             : 
      22           2 : static void do_chainlock(const char *name, int tdb_flags, int up, int down)
      23             : {
      24             :         struct tdb_context *tdb;
      25             :         int ret;
      26             :         ssize_t nread, nwritten;
      27           2 :         char c = 0;
      28             : 
      29           2 :         tdb = tdb_open_ex(name, 3, tdb_flags,
      30             :                           O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
      31           2 :         ok(tdb, "tdb_open_ex should succeed");
      32             : 
      33           2 :         ret = tdb_chainlock(tdb, key);
      34           2 :         ok(ret == 0, "tdb_chainlock should succeed");
      35             : 
      36           2 :         nwritten = write(up, &c, sizeof(c));
      37           2 :         ok(nwritten == sizeof(c), "write should succeed");
      38             : 
      39           2 :         nread = read(down, &c, sizeof(c));
      40           2 :         ok(nread == sizeof(c), "read should succeed");
      41             : 
      42           2 :         exit(0);
      43             : }
      44             : 
      45           2 : static void do_allrecord_lock(const char *name, int tdb_flags, int up, int down)
      46             : {
      47             :         struct tdb_context *tdb;
      48             :         int ret;
      49             :         ssize_t nread, nwritten;
      50           2 :         char c = 0;
      51             : 
      52           2 :         tdb = tdb_open_ex(name, 3, tdb_flags,
      53             :                           O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
      54           2 :         ok(tdb, "tdb_open_ex should succeed");
      55             : 
      56           2 :         ret = tdb_allrecord_lock(tdb, F_WRLCK, TDB_LOCK_WAIT, false);
      57           2 :         ok(ret == 0, "tdb_allrecord_lock should succeed");
      58             : 
      59           2 :         nwritten = write(up, &c, sizeof(c));
      60           2 :         ok(nwritten == sizeof(c), "write should succeed");
      61             : 
      62           2 :         nread = read(down, &c, sizeof(c));
      63           2 :         ok(nread == sizeof(c), "read should succeed");
      64             : 
      65           2 :         exit(0);
      66             : }
      67             : 
      68             : /* The code should barf on TDBs created with rwlocks. */
      69           2 : static int do_tests(const char *name, int tdb_flags)
      70             : {
      71             :         struct tdb_context *tdb;
      72             :         int ret;
      73             :         pid_t chainlock_child, allrecord_child;
      74             :         int chainlock_down[2];
      75             :         int chainlock_up[2];
      76             :         int allrecord_down[2];
      77             :         int allrecord_up[2];
      78             :         char c;
      79             :         ssize_t nread, nwritten;
      80             : 
      81           2 :         key.dsize = strlen("hi");
      82           2 :         key.dptr = discard_const_p(uint8_t, "hi");
      83           2 :         data.dsize = strlen("world");
      84           2 :         data.dptr = discard_const_p(uint8_t, "world");
      85             : 
      86           2 :         ret = pipe(chainlock_down);
      87           2 :         ok(ret == 0, "pipe should succeed");
      88             : 
      89           2 :         ret = pipe(chainlock_up);
      90           2 :         ok(ret == 0, "pipe should succeed");
      91             : 
      92           2 :         ret = pipe(allrecord_down);
      93           2 :         ok(ret == 0, "pipe should succeed");
      94             : 
      95           2 :         ret = pipe(allrecord_up);
      96           2 :         ok(ret == 0, "pipe should succeed");
      97             : 
      98           2 :         chainlock_child = fork();
      99           4 :         ok(chainlock_child != -1, "fork should succeed");
     100             : 
     101           4 :         if (chainlock_child == 0) {
     102           2 :                 close(chainlock_up[0]);
     103           2 :                 close(chainlock_down[1]);
     104           2 :                 close(allrecord_up[0]);
     105           2 :                 close(allrecord_up[1]);
     106           2 :                 close(allrecord_down[0]);
     107           2 :                 close(allrecord_down[1]);
     108           2 :                 do_chainlock(name, tdb_flags,
     109             :                              chainlock_up[1], chainlock_down[0]);
     110           0 :                 exit(0);
     111             :         }
     112           2 :         close(chainlock_up[1]);
     113           2 :         close(chainlock_down[0]);
     114             : 
     115           2 :         nread = read(chainlock_up[0], &c, sizeof(c));
     116           2 :         ok(nread == sizeof(c), "read should succeed");
     117             : 
     118             :         /*
     119             :          * Now we have a process holding a chainlock. Start another process
     120             :          * trying the allrecord lock. This will block.
     121             :          */
     122             : 
     123           2 :         allrecord_child = fork();
     124           4 :         ok(allrecord_child != -1, "fork should succeed");
     125             : 
     126           4 :         if (allrecord_child == 0) {
     127           2 :                 close(chainlock_up[0]);
     128           2 :                 close(chainlock_up[1]);
     129           2 :                 close(chainlock_down[0]);
     130           2 :                 close(chainlock_down[1]);
     131           2 :                 close(allrecord_up[0]);
     132           2 :                 close(allrecord_down[1]);
     133           2 :                 do_allrecord_lock(name, tdb_flags,
     134             :                                   allrecord_up[1], allrecord_down[0]);
     135           0 :                 exit(0);
     136             :         }
     137           2 :         close(allrecord_up[1]);
     138           2 :         close(allrecord_down[0]);
     139             : 
     140           2 :         poll(NULL, 0, 500);
     141             : 
     142           2 :         tdb = tdb_open_ex(name, 3, tdb_flags,
     143             :                           O_RDWR|O_CREAT, 0755, &taplogctx, NULL);
     144           2 :         ok(tdb, "tdb_open_ex should succeed");
     145             : 
     146             :         /*
     147             :          * Someone already holds a chainlock, but we're able to get the
     148             :          * freelist lock.
     149             :          *
     150             :          * The freelist lock/mutex is independent from the allrecord lock/mutex.
     151             :          */
     152             : 
     153           2 :         ret = tdb_chainlock_nonblock(tdb, key);
     154           2 :         ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
     155             : 
     156           2 :         ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
     157           2 :         ok(ret == 0, "tdb_lock_nonblock should succeed");
     158             : 
     159           2 :         ret = tdb_unlock(tdb, -1, F_WRLCK);
     160           2 :         ok(ret == 0, "tdb_unlock should succeed");
     161             : 
     162             :         /*
     163             :          * We have someone else having done the lock for us. Just mark it.
     164             :          */
     165             : 
     166           2 :         ret = tdb_chainlock_mark(tdb, key);
     167           2 :         ok(ret == 0, "tdb_chainlock_mark should succeed");
     168             : 
     169             :         /*
     170             :          * The tdb_store below will block the freelist. In one version of the
     171             :          * mutex patches, the freelist was already blocked here by the
     172             :          * allrecord child, which was waiting for the chainlock child to give
     173             :          * up its chainlock. Make sure that we don't run into this
     174             :          * deadlock. To exercise the deadlock, just comment out the "ok"
     175             :          * line.
     176             :          *
     177             :          * The freelist lock/mutex is independent from the allrecord lock/mutex.
     178             :          */
     179             : 
     180           2 :         ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
     181           2 :         ok(ret == 0, "tdb_lock_nonblock should succeed");
     182             : 
     183           2 :         ret = tdb_unlock(tdb, -1, F_WRLCK);
     184           2 :         ok(ret == 0, "tdb_unlock should succeed");
     185             : 
     186           2 :         ret = tdb_store(tdb, key, data, TDB_INSERT);
     187           2 :         ok(ret == 0, "tdb_store should succeed");
     188             : 
     189           2 :         ret = tdb_chainlock_unmark(tdb, key);
     190           2 :         ok(ret == 0, "tdb_chainlock_unmark should succeed");
     191             : 
     192           2 :         nwritten = write(chainlock_down[1], &c, sizeof(c));
     193           2 :         ok(nwritten == sizeof(c), "write should succeed");
     194             : 
     195           2 :         nread = read(chainlock_up[0], &c, sizeof(c));
     196           2 :         ok(nread == 0, "read should succeed");
     197             : 
     198           2 :         nread = read(allrecord_up[0], &c, sizeof(c));
     199           2 :         ok(nread == sizeof(c), "read should succeed");
     200             : 
     201             :         /*
     202             :          * Someone already holds the allrecord lock, but we're able to get the
     203             :          * freelist lock.
     204             :          *
     205             :          * The freelist lock/mutex is independent from the allrecord lock/mutex.
     206             :          */
     207             : 
     208           2 :         ret = tdb_chainlock_nonblock(tdb, key);
     209           2 :         ok(ret == -1, "tdb_chainlock_nonblock should not succeed");
     210             : 
     211           2 :         ret = tdb_lockall_nonblock(tdb);
     212           2 :         ok(ret == -1, "tdb_lockall_nonblock should not succeed");
     213             : 
     214           2 :         ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
     215           2 :         ok(ret == 0, "tdb_lock_nonblock should succeed");
     216             : 
     217           2 :         ret = tdb_unlock(tdb, -1, F_WRLCK);
     218           2 :         ok(ret == 0, "tdb_unlock should succeed");
     219             : 
     220             :         /*
     221             :          * We have someone else having done the lock for us. Just mark it.
     222             :          */
     223             : 
     224           2 :         ret = tdb_lockall_mark(tdb);
     225           2 :         ok(ret == 0, "tdb_lockall_mark should succeed");
     226             : 
     227           2 :         ret = tdb_lock_nonblock(tdb, -1, F_WRLCK);
     228           2 :         ok(ret == 0, "tdb_lock_nonblock should succeed");
     229             : 
     230           2 :         ret = tdb_unlock(tdb, -1, F_WRLCK);
     231           2 :         ok(ret == 0, "tdb_unlock should succeed");
     232             : 
     233           2 :         ret = tdb_store(tdb, key, data, TDB_REPLACE);
     234           2 :         ok(ret == 0, "tdb_store should succeed");
     235             : 
     236           2 :         ret = tdb_lockall_unmark(tdb);
     237           2 :         ok(ret == 0, "tdb_lockall_unmark should succeed");
     238             : 
     239           2 :         nwritten = write(allrecord_down[1], &c, sizeof(c));
     240           2 :         ok(nwritten == sizeof(c), "write should succeed");
     241             : 
     242           2 :         nread = read(allrecord_up[0], &c, sizeof(c));
     243           2 :         ok(nread == 0, "read should succeed");
     244             : 
     245           2 :         close(chainlock_up[0]);
     246           2 :         close(chainlock_down[1]);
     247           2 :         close(allrecord_up[0]);
     248           2 :         close(allrecord_down[1]);
     249           2 :         diag("%s tests done", name);
     250           2 :         return exit_status();
     251             : }
     252             : 
     253           1 : int main(int argc, char *argv[])
     254             : {
     255             :         int ret;
     256             :         bool mutex_support;
     257             : 
     258           1 :         mutex_support = tdb_runtime_check_for_robust_mutexes();
     259             : 
     260           1 :         ret = do_tests("marklock-deadlock-fcntl.tdb",
     261             :                        TDB_CLEAR_IF_FIRST |
     262             :                        TDB_INCOMPATIBLE_HASH);
     263           1 :         ok(ret == 0, "marklock-deadlock-fcntl.tdb tests should succeed");
     264             : 
     265           1 :         if (!mutex_support) {
     266           0 :                 skip(1, "No robust mutex support, "
     267             :                         "skipping marklock-deadlock-mutex.tdb tests");
     268           0 :                 return exit_status();
     269             :         }
     270             : 
     271           1 :         ret = do_tests("marklock-deadlock-mutex.tdb",
     272             :                        TDB_CLEAR_IF_FIRST |
     273             :                        TDB_MUTEX_LOCKING |
     274             :                        TDB_INCOMPATIBLE_HASH);
     275           1 :         ok(ret == 0, "marklock-deadlock-mutex.tdb tests should succeed");
     276             : 
     277           1 :         return exit_status();
     278             : }

Generated by: LCOV version 1.14