1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 // vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
3 #ident "$Id$"
4 /*======
5 This file is part of PerconaFT.
6 
7 
8 Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
9 
10     PerconaFT is free software: you can redistribute it and/or modify
11     it under the terms of the GNU General Public License, version 2,
12     as published by the Free Software Foundation.
13 
14     PerconaFT is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
21 
22 ----------------------------------------
23 
24     PerconaFT is free software: you can redistribute it and/or modify
25     it under the terms of the GNU Affero General Public License, version 3,
26     as published by the Free Software Foundation.
27 
28     PerconaFT is distributed in the hope that it will be useful,
29     but WITHOUT ANY WARRANTY; without even the implied warranty of
30     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31     GNU Affero General Public License for more details.
32 
33     You should have received a copy of the GNU Affero General Public License
34     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
35 ======= */
36 
37 #ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
38 
39 // this test demonstrates that the lock manager can detect a simple deadlock with 2 transactions on 2 threads
40 // the threads do:
41 // T(a) put 0, grabs write lock on 0
42 // T(b) put N-1, grabs write lock on N-1
43 // T(a) put N-1, try's to grab write lock on N-1, should return lock not granted
44 // T(b) put 0, try's to grab write lock on 0, should return deadlock
45 // T(b) abort
46 // T(a) gets lock W(N-1)
47 // T(A) commit
48 
49 #include "test.h"
50 #include "toku_pthread.h"
51 
52 struct test_seq {
53     int state;
54     toku_mutex_t lock;
55     toku_cond_t cv;
56 };
57 
test_seq_init(struct test_seq * seq)58 static void test_seq_init(struct test_seq *seq) {
59     seq->state = 0;
60     toku_mutex_init(toku_uninstrumented, &seq->lock, nullptr);
61     toku_cond_init(toku_uninstrumented, &seq->cv, nullptr);
62 }
63 
test_seq_destroy(struct test_seq * seq)64 static void test_seq_destroy(struct test_seq *seq) {
65     toku_mutex_destroy(&seq->lock);
66     toku_cond_destroy(&seq->cv);
67 }
68 
test_seq_sleep(struct test_seq * seq,int new_state)69 static void test_seq_sleep(struct test_seq *seq, int new_state) {
70     toku_mutex_lock(&seq->lock);
71     while (seq->state != new_state) {
72         toku_cond_wait(&seq->cv, &seq->lock);
73     }
74     toku_mutex_unlock(&seq->lock);
75 }
76 
test_seq_next_state(struct test_seq * seq)77 static void test_seq_next_state(struct test_seq *seq) {
78     toku_mutex_lock(&seq->lock);
79     seq->state++;
80     toku_cond_broadcast(&seq->cv);
81     toku_mutex_unlock(&seq->lock);
82 }
83 
insert_row(DB * db,DB_TXN * txn,int k,int v,int expect_r)84 static void insert_row(DB *db, DB_TXN *txn, int k, int v, int expect_r) {
85     DBT key; dbt_init(&key, &k, sizeof k);
86     DBT value; dbt_init(&value, &v, sizeof v);
87     int r = db->put(db, txn, &key, &value, 0); assert(r == expect_r);
88 }
89 
90 struct run_txn_b_arg {
91     struct test_seq *test_seq;
92     DB_TXN *txn_b;
93     DB *db;
94     int n;
95 };
96 
run_txn_b(void * arg)97 static void *run_txn_b(void *arg) {
98     struct run_txn_b_arg *b_arg = (struct run_txn_b_arg *) arg;
99     struct test_seq *test_seq = b_arg->test_seq;
100     DB_TXN *txn_b = b_arg->txn_b;
101     DB *db = b_arg->db;
102     int n = b_arg->n;
103 
104     test_seq_sleep(test_seq, 1);
105     insert_row(db, txn_b, htonl(n-1), n-1, 0);
106     test_seq_next_state(test_seq);
107 
108     test_seq_sleep(test_seq, 3);
109     insert_row(db, txn_b, htonl(0), 0, DB_LOCK_NOTGRANTED);
110     test_seq_next_state(test_seq);
111 
112     test_seq_sleep(test_seq, 5);
113     int r = txn_b->abort(txn_b); assert(r == 0);
114 
115     return arg;
116 }
117 
simple_deadlock(DB_ENV * db_env,DB * db,int do_txn,int n)118 static void simple_deadlock(DB_ENV *db_env, DB *db, int do_txn, int n) {
119     int r;
120 
121     DB_TXN *txn_init = NULL;
122     if (do_txn) {
123         r = db_env->txn_begin(db_env, NULL, &txn_init, 0); assert(r == 0);
124     }
125 
126     for (int k = 0; k < n; k++) {
127         insert_row(db, txn_init, htonl(k), k, 0);
128     }
129 
130     if (do_txn) {
131         r = txn_init->commit(txn_init, 0); assert(r == 0);
132     }
133 
134     uint32_t txn_flags = 0;
135 
136     DB_TXN *txn_a = NULL;
137     if (do_txn) {
138         r = db_env->txn_begin(db_env, NULL, &txn_a, txn_flags); assert(r == 0);
139     }
140 
141     DB_TXN *txn_b = NULL;
142     if (do_txn) {
143         r = db_env->txn_begin(db_env, NULL, &txn_b, txn_flags); assert(r == 0);
144     }
145 
146     struct test_seq test_seq; ZERO_STRUCT(test_seq); test_seq_init(&test_seq);
147 
148     toku_pthread_t tid;
149     struct run_txn_b_arg arg = {&test_seq, txn_b, db, n};
150     r = toku_pthread_create(
151         toku_uninstrumented, &tid, nullptr, run_txn_b, &arg);
152 
153     test_seq_sleep(&test_seq, 0);
154     insert_row(db, txn_a, htonl(0), 0, 0);
155     test_seq_next_state(&test_seq);
156 
157     test_seq_sleep(&test_seq, 2);
158     insert_row(db, txn_a, htonl(n-1), n-1, DB_LOCK_NOTGRANTED);
159     test_seq_next_state(&test_seq);
160 
161     test_seq_sleep(&test_seq, 4);
162     if (do_txn) {
163         r = txn_a->abort(txn_a); assert(r == 0);
164     }
165     test_seq_next_state(&test_seq);
166 
167     void *ret = NULL;
168     r = toku_pthread_join(tid, &ret); assert(r == 0);
169 
170     test_seq_destroy(&test_seq);
171 }
172 
test_main(int argc,char * const argv[])173 int test_main(int argc, char * const argv[]) {
174     uint64_t cachesize = 0;
175     uint32_t pagesize = 0;
176     int do_txn = 1;
177     int nrows = 1000;
178     const char *db_env_dir = TOKU_TEST_FILENAME;
179     const char *db_filename = "simple_deadlock";
180     int db_env_open_flags = DB_CREATE | DB_PRIVATE | DB_INIT_MPOOL | DB_INIT_TXN | DB_INIT_LOCK | DB_INIT_LOG | DB_THREAD;
181 
182     // parse_args(argc, argv);
183     for (int i = 1; i < argc; i++) {
184         if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
185             verbose++;
186             continue;
187         }
188         if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) {
189             if (verbose > 0)
190                 verbose--;
191             continue;
192         }
193         if (strcmp(argv[i], "-n") == 0 && i+1 < argc) {
194             nrows = atoi(argv[++i]);
195             continue;
196         }
197         assert(0);
198     }
199 
200     // setup env
201     int r;
202     char rm_cmd[strlen(db_env_dir) + strlen("rm -rf ") + 1];
203     snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf %s", db_env_dir);
204     r = system(rm_cmd); assert(r == 0);
205 
206     r = toku_os_mkdir(db_env_dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); assert(r == 0);
207 
208     DB_ENV *db_env = NULL;
209     r = db_env_create(&db_env, 0); assert(r == 0);
210     if (cachesize) {
211         const uint64_t gig = 1 << 30;
212         r = db_env->set_cachesize(db_env, cachesize / gig, cachesize % gig, 1); assert(r == 0);
213     }
214     if (!do_txn)
215         db_env_open_flags &= ~(DB_INIT_TXN | DB_INIT_LOG);
216     r = db_env->open(db_env, db_env_dir, db_env_open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); assert(r == 0);
217     r = db_env->set_lock_timeout(db_env, 0, nullptr); assert(r == 0); // no wait
218 
219     // create the db
220     DB *db = NULL;
221     r = db_create(&db, db_env, 0); assert(r == 0);
222     DB_TXN *create_txn = NULL;
223     if (do_txn) {
224         r = db_env->txn_begin(db_env, NULL, &create_txn, 0); assert(r == 0);
225     }
226     if (pagesize) {
227         r = db->set_pagesize(db, pagesize); assert(r == 0);
228     }
229     r = db->open(db, create_txn, db_filename, NULL, DB_BTREE, DB_CREATE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); assert(r == 0);
230     if (do_txn) {
231         r = create_txn->commit(create_txn, 0); assert(r == 0);
232     }
233 
234     // run test
235     simple_deadlock(db_env, db, do_txn, nrows);
236 
237     // close env
238     r = db->close(db, 0); assert(r == 0); db = NULL;
239     r = db_env->close(db_env, 0); assert(r == 0); db_env = NULL;
240 
241     return 0;
242 }
243