1 | /***************************************************************************** |
2 | |
3 | Copyright (c) 2011, 2015, Oracle and/or its affiliates. All Rights Reserved. |
4 | Copyright (c) 2015, 2018, MariaDB Corporation. |
5 | |
6 | Portions of this file contain modifications contributed and copyrighted by |
7 | Google, Inc. Those modifications are gratefully acknowledged and are described |
8 | briefly in the InnoDB documentation. The contributions by Google are |
9 | incorporated with their permission, and subject to the conditions contained in |
10 | the file COPYING.Google. |
11 | |
12 | Portions of this file contain modifications contributed and copyrighted |
13 | by Percona Inc.. Those modifications are |
14 | gratefully acknowledged and are described briefly in the InnoDB |
15 | documentation. The contributions by Percona Inc. are incorporated with |
16 | their permission, and subject to the conditions contained in the file |
17 | COPYING.Percona. |
18 | |
19 | This program is free software; you can redistribute it and/or modify it under |
20 | the terms of the GNU General Public License as published by the Free Software |
21 | Foundation; version 2 of the License. |
22 | |
23 | This program is distributed in the hope that it will be useful, but WITHOUT |
24 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
25 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
26 | |
27 | You should have received a copy of the GNU General Public License along with |
28 | this program; if not, write to the Free Software Foundation, Inc., |
29 | 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA |
30 | |
31 | *****************************************************************************/ |
32 | |
33 | /**************************************************//** |
34 | @file srv/srv0conc.cc |
35 | |
36 | InnoDB concurrency manager |
37 | |
38 | Created 2011/04/18 Sunny Bains |
39 | *******************************************************/ |
40 | |
41 | #include "ha_prototypes.h" |
42 | #include <mysql/service_thd_wait.h> |
43 | |
44 | #include "srv0srv.h" |
45 | #include "trx0trx.h" |
46 | #include "row0mysql.h" |
47 | #include "dict0dict.h" |
48 | #include <mysql/service_wsrep.h> |
49 | |
50 | /** Number of times a thread is allowed to enter InnoDB within the same |
51 | SQL query after it has once got the ticket. */ |
52 | ulong srv_n_free_tickets_to_enter = 500; |
53 | |
54 | /** Maximum sleep delay (in micro-seconds), value of 0 disables it. */ |
55 | ulong srv_adaptive_max_sleep_delay = 150000; |
56 | |
57 | ulong srv_thread_sleep_delay = 10000; |
58 | |
59 | |
60 | /** We are prepared for a situation that we have this many threads waiting for |
61 | a semaphore inside InnoDB. srv_start() sets the value. */ |
62 | ulint srv_max_n_threads; |
63 | |
64 | /** The following controls how many threads we let inside InnoDB concurrently: |
65 | threads waiting for locks are not counted into the number because otherwise |
66 | we could get a deadlock. Value of 0 will disable the concurrency check. */ |
67 | |
68 | ulong srv_thread_concurrency = 0; |
69 | |
70 | /** Variables tracking the active and waiting threads. */ |
71 | struct srv_conc_t { |
72 | char pad[CACHE_LINE_SIZE - (sizeof(ulint) + sizeof(lint))]; |
73 | |
74 | /** Number of transactions that have declared_to_be_inside_innodb */ |
75 | ulint n_active; |
76 | |
77 | /** Number of OS threads waiting in the FIFO for permission to |
78 | enter InnoDB */ |
79 | ulint n_waiting; |
80 | }; |
81 | |
82 | /* Control variables for tracking concurrency. */ |
83 | static srv_conc_t srv_conc; |
84 | |
85 | /*********************************************************************//** |
86 | Note that a user thread is entering InnoDB. */ |
87 | static |
88 | void |
89 | srv_enter_innodb_with_tickets( |
90 | /*==========================*/ |
91 | trx_t* trx) /*!< in/out: transaction that wants |
92 | to enter InnoDB */ |
93 | { |
94 | trx->declared_to_be_inside_innodb = TRUE; |
95 | trx->n_tickets_to_enter_innodb = srv_n_free_tickets_to_enter; |
96 | } |
97 | |
98 | /*********************************************************************//** |
99 | Handle the scheduling of a user thread that wants to enter InnoDB. Setting |
100 | srv_adaptive_max_sleep_delay > 0 switches the adaptive sleep calibration to |
101 | ON. When set, we want to wait in the queue for as little time as possible. |
102 | However, very short waits will result in a lot of context switches and that |
103 | is also not desirable. When threads need to sleep multiple times we increment |
104 | os_thread_sleep_delay by one. When we see threads getting a slot without |
105 | waiting and there are no other threads waiting in the queue, we try and reduce |
106 | the wait as much as we can. Currently we reduce it by half each time. If the |
107 | thread only had to wait for one turn before it was able to enter InnoDB we |
108 | decrement it by one. This is to try and keep the sleep time stable around the |
109 | "optimum" sleep time. */ |
110 | static |
111 | void |
112 | srv_conc_enter_innodb_with_atomics( |
113 | /*===============================*/ |
114 | trx_t* trx) /*!< in/out: transaction that wants |
115 | to enter InnoDB */ |
116 | { |
117 | ulint n_sleeps = 0; |
118 | ibool notified_mysql = FALSE; |
119 | |
120 | ut_a(!trx->declared_to_be_inside_innodb); |
121 | |
122 | for (;;) { |
123 | ulint sleep_in_us; |
124 | #ifdef WITH_WSREP |
125 | if (wsrep_on(trx->mysql_thd) && |
126 | wsrep_trx_is_aborting(trx->mysql_thd)) { |
127 | if (wsrep_debug) { |
128 | ib::info() << |
129 | "srv_conc_enter due to MUST_ABORT" ; |
130 | } |
131 | srv_conc_force_enter_innodb(trx); |
132 | return; |
133 | } |
134 | #endif /* WITH_WSREP */ |
135 | |
136 | if (srv_thread_concurrency == 0) { |
137 | if (notified_mysql) { |
138 | my_atomic_addlint(&srv_conc.n_waiting, |
139 | ulint(-1)); |
140 | thd_wait_end(trx->mysql_thd); |
141 | } |
142 | |
143 | return; |
144 | } |
145 | |
146 | if (srv_conc.n_active < srv_thread_concurrency) { |
147 | ulint n_active; |
148 | |
149 | /* Check if there are any free tickets. */ |
150 | n_active = my_atomic_addlint( |
151 | &srv_conc.n_active, 1) + 1; |
152 | |
153 | if (n_active <= srv_thread_concurrency) { |
154 | |
155 | srv_enter_innodb_with_tickets(trx); |
156 | |
157 | if (notified_mysql) { |
158 | my_atomic_addlint(&srv_conc.n_waiting, |
159 | ulint(-1)); |
160 | thd_wait_end(trx->mysql_thd); |
161 | } |
162 | |
163 | if (srv_adaptive_max_sleep_delay > 0) { |
164 | if (srv_thread_sleep_delay > 20 |
165 | && n_sleeps == 1) { |
166 | |
167 | --srv_thread_sleep_delay; |
168 | } |
169 | |
170 | if (srv_conc.n_waiting == 0) { |
171 | srv_thread_sleep_delay >>= 1; |
172 | } |
173 | } |
174 | |
175 | return; |
176 | } |
177 | |
178 | /* Since there were no free seats, we relinquish |
179 | the overbooked ticket. */ |
180 | |
181 | my_atomic_addlint(&srv_conc.n_active, ulint(-1)); |
182 | } |
183 | |
184 | if (!notified_mysql) { |
185 | my_atomic_addlint(&srv_conc.n_waiting, 1); |
186 | |
187 | thd_wait_begin(trx->mysql_thd, THD_WAIT_USER_LOCK); |
188 | |
189 | notified_mysql = TRUE; |
190 | } |
191 | |
192 | DEBUG_SYNC_C("user_thread_waiting" ); |
193 | trx->op_info = "sleeping before entering InnoDB" ; |
194 | |
195 | sleep_in_us = srv_thread_sleep_delay; |
196 | |
197 | /* Guard against overflow when adaptive sleep delay is on. */ |
198 | |
199 | if (srv_adaptive_max_sleep_delay > 0 |
200 | && sleep_in_us > srv_adaptive_max_sleep_delay) { |
201 | |
202 | sleep_in_us = srv_adaptive_max_sleep_delay; |
203 | srv_thread_sleep_delay = static_cast<ulong>(sleep_in_us); |
204 | } |
205 | |
206 | os_thread_sleep(sleep_in_us); |
207 | |
208 | trx->op_info = "" ; |
209 | |
210 | ++n_sleeps; |
211 | |
212 | if (srv_adaptive_max_sleep_delay > 0 && n_sleeps > 1) { |
213 | ++srv_thread_sleep_delay; |
214 | } |
215 | } |
216 | } |
217 | |
218 | /*********************************************************************//** |
219 | Note that a user thread is leaving InnoDB code. */ |
220 | static |
221 | void |
222 | srv_conc_exit_innodb_with_atomics( |
223 | /*==============================*/ |
224 | trx_t* trx) /*!< in/out: transaction */ |
225 | { |
226 | trx->n_tickets_to_enter_innodb = 0; |
227 | trx->declared_to_be_inside_innodb = FALSE; |
228 | |
229 | my_atomic_addlint(&srv_conc.n_active, ulint(-1)); |
230 | } |
231 | |
232 | /*********************************************************************//** |
233 | Puts an OS thread to wait if there are too many concurrent threads |
234 | (>= srv_thread_concurrency) inside InnoDB. The threads wait in a FIFO queue. |
235 | @param[in,out] prebuilt row prebuilt handler */ |
236 | void |
237 | srv_conc_enter_innodb( |
238 | row_prebuilt_t* prebuilt) |
239 | { |
240 | trx_t* trx = prebuilt->trx; |
241 | |
242 | ut_ad(!sync_check_iterate(sync_check())); |
243 | |
244 | srv_conc_enter_innodb_with_atomics(trx); |
245 | } |
246 | |
247 | /*********************************************************************//** |
248 | This lets a thread enter InnoDB regardless of the number of threads inside |
249 | InnoDB. This must be called when a thread ends a lock wait. */ |
250 | void |
251 | srv_conc_force_enter_innodb( |
252 | /*========================*/ |
253 | trx_t* trx) /*!< in: transaction object associated with the |
254 | thread */ |
255 | { |
256 | ut_ad(!sync_check_iterate(sync_check())); |
257 | |
258 | if (!srv_thread_concurrency) { |
259 | |
260 | return; |
261 | } |
262 | |
263 | (void) my_atomic_addlint(&srv_conc.n_active, 1); |
264 | |
265 | trx->n_tickets_to_enter_innodb = 1; |
266 | trx->declared_to_be_inside_innodb = TRUE; |
267 | } |
268 | |
269 | /*********************************************************************//** |
270 | This must be called when a thread exits InnoDB in a lock wait or at the |
271 | end of an SQL statement. */ |
272 | void |
273 | srv_conc_force_exit_innodb( |
274 | /*=======================*/ |
275 | trx_t* trx) /*!< in: transaction object associated with the |
276 | thread */ |
277 | { |
278 | if ((trx->mysql_thd != NULL |
279 | && thd_is_replication_slave_thread(trx->mysql_thd)) |
280 | || trx->declared_to_be_inside_innodb == FALSE) { |
281 | |
282 | return; |
283 | } |
284 | |
285 | srv_conc_exit_innodb_with_atomics(trx); |
286 | |
287 | ut_ad(!sync_check_iterate(sync_check())); |
288 | } |
289 | |
290 | /*********************************************************************//** |
291 | Get the count of threads waiting inside InnoDB. */ |
292 | ulint |
293 | srv_conc_get_waiting_threads(void) |
294 | /*==============================*/ |
295 | { |
296 | return(srv_conc.n_waiting); |
297 | } |
298 | |
299 | /*********************************************************************//** |
300 | Get the count of threads active inside InnoDB. */ |
301 | ulint |
302 | srv_conc_get_active_threads(void) |
303 | /*==============================*/ |
304 | { |
305 | return(srv_conc.n_active); |
306 | } |
307 | |
308 | #ifdef WITH_WSREP |
309 | UNIV_INTERN |
310 | void |
311 | wsrep_srv_conc_cancel_wait( |
312 | /*=======================*/ |
313 | trx_t* trx) /*!< in: transaction object associated with the |
314 | thread */ |
315 | { |
316 | #ifdef HAVE_ATOMIC_BUILTINS |
317 | /* aborting transactions will enter innodb by force in |
318 | srv_conc_enter_innodb_with_atomics(). No need to cancel here, |
319 | thr will wake up after os_sleep and let to enter innodb |
320 | */ |
321 | if (wsrep_debug) { |
322 | ib::info() << "WSREP: conc slot cancel, no atomics" ; |
323 | } |
324 | #else |
325 | // JAN: TODO: MySQL 5.7 |
326 | //os_fast_mutex_lock(&srv_conc_mutex); |
327 | if (trx->wsrep_event) { |
328 | if (wsrep_debug) { |
329 | ib::info() << "WSREP: conc slot cancel" ; |
330 | } |
331 | os_event_set(trx->wsrep_event); |
332 | } |
333 | //os_fast_mutex_unlock(&srv_conc_mutex); |
334 | #endif |
335 | } |
336 | #endif /* WITH_WSREP */ |
337 | |
338 | |