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