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 verifies that small txn's do not get stalled for a long time by lock escalation.
40 // Two lock trees are used by the test: a big lock tree and a small lock tree.
41 // One big txn grabs lots of write locks on the big lock tree.
42 // Several small txn's grab a single write lock on the small lock tree.
43 // None of the locks conflict.
44 // Eventually, the locks for the big txn consume all of the lock tree memory, so lock escalation runs.
45 // The test measures the lock acquisition time and makes sure that the small txn's are not blocked for
46
47 // locktree_escalation_stalls -v --stalls 10
48 // verify that only big txn's get tagged with > 1 second stalls
49
50 #include <stdio.h>
51 #include "locktree.h"
52 #include "test.h"
53
54 using namespace toku;
55
56 static int verbose = 0;
57 static int killed = 0;
58
locktree_release_lock(locktree * lt,TXNID txn_id,int64_t left_k,int64_t right_k)59 static void locktree_release_lock(locktree *lt, TXNID txn_id, int64_t left_k, int64_t right_k) {
60 range_buffer buffer;
61 buffer.create();
62 DBT left; toku_fill_dbt(&left, &left_k, sizeof left_k);
63 DBT right; toku_fill_dbt(&right, &right_k, sizeof right_k);
64 buffer.append(&left, &right);
65 lt->release_locks(txn_id, &buffer);
66 buffer.destroy();
67 }
68
69 // grab a write range lock on int64 keys bounded by left_k and right_k
locktree_write_lock(locktree * lt,TXNID txn_id,int64_t left_k,int64_t right_k,bool big_txn)70 static int locktree_write_lock(locktree *lt, TXNID txn_id, int64_t left_k, int64_t right_k, bool big_txn) {
71 DBT left; toku_fill_dbt(&left, &left_k, sizeof left_k);
72 DBT right; toku_fill_dbt(&right, &right_k, sizeof right_k);
73 return lt->acquire_write_lock(txn_id, &left, &right, nullptr, big_txn);
74 }
75
run_big_txn(locktree_manager * mgr UU (),locktree * lt,TXNID txn_id)76 static void run_big_txn(locktree_manager *mgr UU(), locktree *lt, TXNID txn_id) {
77 int64_t last_i = -1;
78 for (int64_t i = 0; !killed; i++) {
79 uint64_t t_start = toku_current_time_microsec();
80 int r = locktree_write_lock(lt, txn_id, i, i, true);
81 assert(r == 0);
82 last_i = i;
83 uint64_t t_end = toku_current_time_microsec();
84 uint64_t t_duration = t_end - t_start;
85 if (t_duration > 100000) {
86 printf("%u %s %" PRId64 " %" PRIu64 "\n", toku_os_gettid(), __FUNCTION__, i, t_duration);
87 }
88 toku_pthread_yield();
89 }
90 if (last_i != -1)
91 locktree_release_lock(lt, txn_id, 0, last_i); // release the range 0 .. last_i
92 }
93
run_small_txn(locktree_manager * mgr UU (),locktree * lt,TXNID txn_id,int64_t k)94 static void run_small_txn(locktree_manager *mgr UU(), locktree *lt, TXNID txn_id, int64_t k) {
95 for (int64_t i = 0; !killed; i++) {
96 uint64_t t_start = toku_current_time_microsec();
97 int r = locktree_write_lock(lt, txn_id, k, k, false);
98 assert(r == 0);
99 uint64_t t_end = toku_current_time_microsec();
100 uint64_t t_duration = t_end - t_start;
101 if (t_duration > 100000) {
102 printf("%u %s %" PRId64 " %" PRIu64 "\n", toku_os_gettid(), __FUNCTION__, i, t_duration);
103 }
104 locktree_release_lock(lt, txn_id, k, k);
105 toku_pthread_yield();
106 }
107 }
108
109 struct arg {
110 locktree_manager *mgr;
111 locktree *lt;
112 TXNID txn_id;
113 int64_t k;
114 };
115
big_f(void * _arg)116 static void *big_f(void *_arg) {
117 struct arg *arg = (struct arg *) _arg;
118 run_big_txn(arg->mgr, arg->lt, arg->txn_id);
119 return arg;
120 }
121
small_f(void * _arg)122 static void *small_f(void *_arg) {
123 struct arg *arg = (struct arg *) _arg;
124 run_small_txn(arg->mgr, arg->lt, arg->txn_id, arg->k);
125 return arg;
126 }
127
e_callback(TXNID txnid,const locktree * lt,const range_buffer & buffer,void * extra)128 static void e_callback(TXNID txnid, const locktree *lt, const range_buffer &buffer, void *extra) {
129 if (verbose)
130 printf("%u %s %" PRIu64 " %p %d %p\n", toku_os_gettid(), __FUNCTION__, txnid, lt, buffer.get_num_ranges(), extra);
131 }
132
get_escalation_count(locktree_manager & mgr)133 static uint64_t get_escalation_count(locktree_manager &mgr) {
134 LTM_STATUS_S ltm_status_test;
135 mgr.get_status(<m_status_test);
136
137 TOKU_ENGINE_STATUS_ROW key_status = NULL;
138 // lookup keyname in status
139 for (int i = 0; ; i++) {
140 TOKU_ENGINE_STATUS_ROW status = <m_status_test.status[i];
141 if (status->keyname == NULL)
142 break;
143 if (strcmp(status->keyname, "LTM_ESCALATION_COUNT") == 0) {
144 key_status = status;
145 break;
146 }
147 }
148 assert(key_status);
149 return key_status->value.num;
150 }
151
main(int argc,const char * argv[])152 int main(int argc, const char *argv[]) {
153 uint64_t stalls = 0;
154 uint64_t max_lock_memory = 1000000000;
155 for (int i = 1; i < argc; i++) {
156 if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) {
157 verbose++;
158 continue;
159 }
160 if (strcmp(argv[i], "--stalls") == 0 && i+1 < argc) {
161 stalls = atoll(argv[++i]);
162 continue;
163 }
164 if (strcmp(argv[i], "--max_lock_memory") == 0 && i+1 < argc) {
165 max_lock_memory = atoll(argv[++i]);
166 continue;
167 }
168 }
169
170 int r;
171
172 // create a manager
173 locktree_manager mgr;
174 mgr.create(nullptr, nullptr, e_callback, nullptr);
175 mgr.set_max_lock_memory(max_lock_memory);
176
177 // create lock trees
178 DICTIONARY_ID dict_id_0 = { .dictid = 1 };
179 locktree *lt_0 = mgr.get_lt(dict_id_0, dbt_comparator, nullptr);
180
181 DICTIONARY_ID dict_id_1 = { .dictid = 2 };
182 locktree *lt_1 = mgr.get_lt(dict_id_1, dbt_comparator, nullptr);
183
184 // create the worker threads
185 struct arg big_arg = {&mgr, lt_0, 1000};
186 pthread_t big_id;
187 r = toku_pthread_create(
188 toku_uninstrumented, &big_id, nullptr, big_f, &big_arg);
189 assert(r == 0);
190
191 const int n_small = 7;
192 pthread_t small_ids[n_small];
193 struct arg small_args[n_small];
194
195 for (int i = 0; i < n_small; i++) {
196 small_args[i] = {&mgr, lt_1, (TXNID)(2000 + i), i};
197 r = toku_pthread_create(toku_uninstrumented,
198 &small_ids[i],
199 nullptr,
200 small_f,
201 &small_args[i]);
202 assert(r == 0);
203 }
204
205 // wait for some escalations to occur
206 while (get_escalation_count(mgr) < stalls) {
207 sleep(1);
208 }
209 killed = 1;
210
211 // cleanup
212 void *ret;
213 r = toku_pthread_join(big_id, &ret);
214 assert(r == 0);
215
216 for (int i = 0; i < n_small; i++) {
217 r = toku_pthread_join(small_ids[i], &ret);
218 assert(r == 0);
219 }
220
221 mgr.release_lt(lt_0);
222 mgr.release_lt(lt_1);
223 mgr.destroy();
224
225 return 0;
226 }
227