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(&ltm_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 = &ltm_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