1/*****************************************************************************
2
3Copyright (c) 2011, 2015, Oracle and/or its affiliates. All Rights Reserved.
4Copyright (c) 2015, 2018, MariaDB Corporation.
5
6Portions of this file contain modifications contributed and copyrighted by
7Google, Inc. Those modifications are gratefully acknowledged and are described
8briefly in the InnoDB documentation. The contributions by Google are
9incorporated with their permission, and subject to the conditions contained in
10the file COPYING.Google.
11
12Portions of this file contain modifications contributed and copyrighted
13by Percona Inc.. Those modifications are
14gratefully acknowledged and are described briefly in the InnoDB
15documentation. The contributions by Percona Inc. are incorporated with
16their permission, and subject to the conditions contained in the file
17COPYING.Percona.
18
19This program is free software; you can redistribute it and/or modify it under
20the terms of the GNU General Public License as published by the Free Software
21Foundation; version 2 of the License.
22
23This program is distributed in the hope that it will be useful, but WITHOUT
24ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
25FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
26
27You should have received a copy of the GNU General Public License along with
28this program; if not, write to the Free Software Foundation, Inc.,
2951 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
30
31*****************************************************************************/
32
33/**************************************************//**
34@file srv/srv0conc.cc
35
36InnoDB concurrency manager
37
38Created 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
51SQL query after it has once got the ticket. */
52ulong srv_n_free_tickets_to_enter = 500;
53
54/** Maximum sleep delay (in micro-seconds), value of 0 disables it. */
55ulong srv_adaptive_max_sleep_delay = 150000;
56
57ulong srv_thread_sleep_delay = 10000;
58
59
60/** We are prepared for a situation that we have this many threads waiting for
61a semaphore inside InnoDB. srv_start() sets the value. */
62ulint srv_max_n_threads;
63
64/** The following controls how many threads we let inside InnoDB concurrently:
65threads waiting for locks are not counted into the number because otherwise
66we could get a deadlock. Value of 0 will disable the concurrency check. */
67
68ulong srv_thread_concurrency = 0;
69
70/** Variables tracking the active and waiting threads. */
71struct 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. */
83static srv_conc_t srv_conc;
84
85/*********************************************************************//**
86Note that a user thread is entering InnoDB. */
87static
88void
89srv_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/*********************************************************************//**
99Handle the scheduling of a user thread that wants to enter InnoDB. Setting
100srv_adaptive_max_sleep_delay > 0 switches the adaptive sleep calibration to
101ON. When set, we want to wait in the queue for as little time as possible.
102However, very short waits will result in a lot of context switches and that
103is also not desirable. When threads need to sleep multiple times we increment
104os_thread_sleep_delay by one. When we see threads getting a slot without
105waiting and there are no other threads waiting in the queue, we try and reduce
106the wait as much as we can. Currently we reduce it by half each time. If the
107thread only had to wait for one turn before it was able to enter InnoDB we
108decrement it by one. This is to try and keep the sleep time stable around the
109"optimum" sleep time. */
110static
111void
112srv_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/*********************************************************************//**
219Note that a user thread is leaving InnoDB code. */
220static
221void
222srv_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/*********************************************************************//**
233Puts 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 */
236void
237srv_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/*********************************************************************//**
248This lets a thread enter InnoDB regardless of the number of threads inside
249InnoDB. This must be called when a thread ends a lock wait. */
250void
251srv_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/*********************************************************************//**
270This must be called when a thread exits InnoDB in a lock wait or at the
271end of an SQL statement. */
272void
273srv_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/*********************************************************************//**
291Get the count of threads waiting inside InnoDB. */
292ulint
293srv_conc_get_waiting_threads(void)
294/*==============================*/
295{
296 return(srv_conc.n_waiting);
297}
298
299/*********************************************************************//**
300Get the count of threads active inside InnoDB. */
301ulint
302srv_conc_get_active_threads(void)
303/*==============================*/
304{
305 return(srv_conc.n_active);
306}
307
308#ifdef WITH_WSREP
309UNIV_INTERN
310void
311wsrep_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