| 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 | |