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 // for all i: T(i) reads 0, gets a read lock on 0
40 // for all i: T(i) writes 0, enters a deadlock
41 // tokudb detects deadlock on the fly
42 // --poll runs the  deadlock detector until all the txns are resolved
43 
44 #include "test.h"
45 #include "toku_pthread.h"
46 #include <portability/toku_atomic.h>
47 
write_row(DB * db,DB_TXN * txn,int k,int v,int expect_r)48 static void write_row(DB *db, DB_TXN *txn, int k, int v, int expect_r) {
49     DBT key; dbt_init(&key, &k, sizeof k);
50     DBT value; dbt_init(&value, &v, sizeof v);
51     int r = db->put(db, txn, &key, &value, 0); assert(r == expect_r);
52 }
53 
read_row(DB * db,DB_TXN * txn,int k,int expect_r)54 static void read_row(DB *db, DB_TXN *txn, int k, int expect_r) {
55     DBT key; dbt_init(&key, &k, sizeof k);
56     DBT value; dbt_init_malloc(&value);
57     int r = db->get(db, txn, &key, &value, 0); assert(r == expect_r);
58     toku_free(value.data);
59 }
60 
61 static volatile int n_txns;
62 
63 struct write_one_arg {
64     DB_TXN *txn;
65     DB *db;
66     int k;
67     int v;
68 };
69 
write_one_f(void * arg)70 static void *write_one_f(void *arg) {
71     struct write_one_arg *f_arg = (struct write_one_arg *) arg;
72     DB_TXN *txn = f_arg->txn;
73     DB *db = f_arg->db;
74     int k = f_arg->k;
75     int v = f_arg->v;
76 
77     DBT key; dbt_init(&key, &k, sizeof k);
78     DBT value; dbt_init(&value, &v, sizeof v);
79     int r = db->put(db, txn, &key, &value, 0);
80     if (verbose)
81         printf("%s %p %d\n", __FUNCTION__, arg, r);
82     assert(r == 0 || r == DB_LOCK_DEADLOCK);
83     if (r == 0) {
84         r = txn->commit(txn, 0); assert(r == 0);
85     } else {
86         r = txn->abort(txn); assert(r == 0);
87     }
88     (void) toku_sync_fetch_and_sub(&n_txns, 1);
89 
90     return arg;
91 }
92 
update_deadlock(DB_ENV * db_env,DB * db,int do_txn,int nrows,int ntxns,int poll_deadlock UU ())93 static void update_deadlock(DB_ENV *db_env, DB *db, int do_txn, int nrows, int ntxns, int poll_deadlock UU()) {
94     int r;
95 
96     // populate the initial tree
97     DB_TXN *txn_init = NULL;
98     if (do_txn) {
99         r = db_env->txn_begin(db_env, NULL, &txn_init, 0); assert(r == 0);
100     }
101     for (int k = 0; k < nrows; k++) {
102         write_row(db, txn_init, htonl(k), k, 0);
103     }
104     if (do_txn) {
105         r = txn_init->commit(txn_init, 0); assert(r == 0);
106     }
107 
108     // create the transactions
109     n_txns = ntxns;
110     DB_TXN *txns[ntxns];
111     for (int i = 0; i < ntxns; i++) {
112         txns[i] = NULL;
113         if (do_txn) {
114             r = db_env->txn_begin(db_env, NULL, &txns[i], 0); assert(r == 0);
115         }
116     }
117 
118 #ifdef BLOCKING_ROW_LOCKS_READS_NOT_SHARED
119     // spice this test up a bit when reads locks are not shared.
120     // test that a dining philosopher's style deadlock is detected
121     // by having each txn take a distinct read lock, and then request
122     // a write lock on the value "next" to it (i + 1 mod ntxns)
123 
124     // get read locks
125     for (int i = 0; i < ntxns; i++) {
126         read_row(db, txns[i], htonl(i), 0);
127     }
128 
129     // get write locks
130     toku_pthread_t tids[ntxns];
131     for (int i = 0; i < ntxns; i++) {
132         struct write_one_arg *XMALLOC(arg);
133         *arg =
134             (struct write_one_arg){txns[i], db, (int)htonl((i + 1) % ntxns), 0};
135         r = toku_pthread_create(
136             toku_uninstrumented, &tids[i], nullptr, write_one_f, arg);
137     }
138 #else
139     // get read locks
140     for (int i = 0; i < ntxns; i++) {
141         read_row(db, txns[i], htonl(0), 0);
142     }
143 
144     // get write locks
145     toku_pthread_t tids[ntxns];
146     for (int i = 0; i < ntxns; i++) {
147         struct write_one_arg *XMALLOC(arg);
148         *arg = (struct write_one_arg){txns[i], db, (int)htonl(0), 0};
149         r = toku_pthread_create(
150             toku_uninstrumented, &tids[i], nullptr, write_one_f, arg);
151     }
152 #endif
153 
154     // cleanup
155     for (int i = 0; i < ntxns; i++) {
156         void *ret = NULL;
157         r = toku_pthread_join(tids[i], &ret); assert(r == 0); toku_free(ret);
158     }
159 }
160 
test_main(int argc,char * const argv[])161 int test_main(int argc, char * const argv[]) {
162     uint64_t cachesize = 0;
163     uint32_t pagesize = 0;
164     int do_txn = 1;
165     int nrows = 1000;
166     int ntxns = 2;
167     int poll_deadlock = 0;
168     const char *db_env_dir = TOKU_TEST_FILENAME;
169     const char *db_filename = "simple_deadlock";
170     int db_env_open_flags = DB_CREATE | DB_PRIVATE | DB_INIT_MPOOL | DB_INIT_TXN | DB_INIT_LOCK | DB_INIT_LOG | DB_THREAD;
171 
172     // parse_args(argc, argv);
173     for (int i = 1; i < argc; i++) {
174         if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
175             verbose++;
176             continue;
177         }
178         if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) {
179             if (verbose > 0)
180                 verbose--;
181             continue;
182         }
183         if (strcmp(argv[i], "--nrows") == 0 && i+1 < argc) {
184             nrows = atoi(argv[++i]);
185             continue;
186         }
187         if (strcmp(argv[i], "--ntxns") == 0 && i+1 < argc) {
188             ntxns = atoi(argv[++i]);
189             continue;
190         }
191         if (strcmp(argv[i], "--poll") == 0) {
192             poll_deadlock = 1;
193             continue;
194         }
195         assert(0);
196     }
197 
198     // setup env
199     int r;
200     char rm_cmd[strlen(db_env_dir) + strlen("rm -rf ") + 1];
201     snprintf(rm_cmd, sizeof(rm_cmd), "rm -rf %s", db_env_dir);
202     r = system(rm_cmd); assert(r == 0);
203 
204     r = toku_os_mkdir(db_env_dir, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); assert(r == 0);
205 
206     DB_ENV *db_env = NULL;
207     r = db_env_create(&db_env, 0); assert(r == 0);
208     if (cachesize) {
209         const uint64_t gig = 1 << 30;
210         r = db_env->set_cachesize(db_env, cachesize / gig, cachesize % gig, 1); assert(r == 0);
211     }
212     if (!do_txn)
213         db_env_open_flags &= ~(DB_INIT_TXN | DB_INIT_LOG);
214     r = db_env->open(db_env, db_env_dir, db_env_open_flags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); assert(r == 0);
215     r = db_env->set_lock_timeout(db_env, 30 * 1000, nullptr); assert(r == 0);
216 
217     // create the db
218     DB *db = NULL;
219     r = db_create(&db, db_env, 0); assert(r == 0);
220     DB_TXN *create_txn = NULL;
221     if (do_txn) {
222         r = db_env->txn_begin(db_env, NULL, &create_txn, 0); assert(r == 0);
223     }
224     if (pagesize) {
225         r = db->set_pagesize(db, pagesize); assert(r == 0);
226     }
227     r = db->open(db, create_txn, db_filename, NULL, DB_BTREE, DB_CREATE, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); assert(r == 0);
228     if (do_txn) {
229         r = create_txn->commit(create_txn, 0); assert(r == 0);
230     }
231 
232     // run test
233     update_deadlock(db_env, db, do_txn, nrows, ntxns, poll_deadlock);
234 
235     // close env
236     r = db->close(db, 0); assert(r == 0); db = NULL;
237     r = db_env->close(db_env, 0); assert(r == 0); db_env = NULL;
238 
239     return 0;
240 }
241