| 1 | /***************************************************************************** |
| 2 | |
| 3 | Copyright (c) 2011, 2017, Oracle and/or its affiliates. All Rights Reserved. |
| 4 | Copyright (c) 2017, MariaDB Corporation. |
| 5 | |
| 6 | This program is free software; you can redistribute it and/or modify it under |
| 7 | the terms of the GNU General Public License as published by the Free Software |
| 8 | Foundation; version 2 of the License. |
| 9 | |
| 10 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 12 | FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. |
| 13 | |
| 14 | You should have received a copy of the GNU General Public License along with |
| 15 | this program; if not, write to the Free Software Foundation, Inc., |
| 16 | 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA |
| 17 | |
| 18 | *****************************************************************************/ |
| 19 | |
| 20 | /**************************************************//** |
| 21 | @file buf/buf0dump.cc |
| 22 | Implements a buffer pool dump/load. |
| 23 | |
| 24 | Created April 08, 2011 Vasil Dimov |
| 25 | *******************************************************/ |
| 26 | |
| 27 | #include "my_global.h" |
| 28 | #include "my_sys.h" |
| 29 | |
| 30 | #include "mysql/psi/mysql_stage.h" |
| 31 | #include "mysql/psi/psi.h" |
| 32 | |
| 33 | #include "univ.i" |
| 34 | |
| 35 | #include "buf0buf.h" |
| 36 | #include "buf0dump.h" |
| 37 | #include "dict0dict.h" |
| 38 | #include "os0file.h" |
| 39 | #include "os0thread.h" |
| 40 | #include "srv0srv.h" |
| 41 | #include "srv0start.h" |
| 42 | #include "sync0rw.h" |
| 43 | #include "ut0byte.h" |
| 44 | |
| 45 | #include <algorithm> |
| 46 | |
| 47 | #include "mysql/service_wsrep.h" /* wsrep_recovery */ |
| 48 | #include <my_service_manager.h> |
| 49 | |
| 50 | enum status_severity { |
| 51 | STATUS_INFO, |
| 52 | STATUS_ERR |
| 53 | }; |
| 54 | |
| 55 | #define SHUTTING_DOWN() (srv_shutdown_state != SRV_SHUTDOWN_NONE) |
| 56 | |
| 57 | /* Flags that tell the buffer pool dump/load thread which action should it |
| 58 | take after being waked up. */ |
| 59 | static volatile bool buf_dump_should_start; |
| 60 | static volatile bool buf_load_should_start; |
| 61 | |
| 62 | static ibool buf_load_abort_flag = FALSE; |
| 63 | |
| 64 | /* Used to temporary store dump info in order to avoid IO while holding |
| 65 | buffer pool mutex during dump and also to sort the contents of the dump |
| 66 | before reading the pages from disk during load. |
| 67 | We store the space id in the high 32 bits and page no in low 32 bits. */ |
| 68 | typedef ib_uint64_t buf_dump_t; |
| 69 | |
| 70 | /* Aux macros to create buf_dump_t and to extract space and page from it */ |
| 71 | #define BUF_DUMP_CREATE(space, page) ut_ull_create(space, page) |
| 72 | #define BUF_DUMP_SPACE(a) ((ulint) ((a) >> 32)) |
| 73 | #define BUF_DUMP_PAGE(a) ((ulint) ((a) & 0xFFFFFFFFUL)) |
| 74 | |
| 75 | /*****************************************************************//** |
| 76 | Wakes up the buffer pool dump/load thread and instructs it to start |
| 77 | a dump. This function is called by MySQL code via buffer_pool_dump_now() |
| 78 | and it should return immediately because the whole MySQL is frozen during |
| 79 | its execution. */ |
| 80 | void |
| 81 | buf_dump_start() |
| 82 | /*============*/ |
| 83 | { |
| 84 | buf_dump_should_start = true; |
| 85 | os_event_set(srv_buf_dump_event); |
| 86 | } |
| 87 | |
| 88 | /*****************************************************************//** |
| 89 | Wakes up the buffer pool dump/load thread and instructs it to start |
| 90 | a load. This function is called by MySQL code via buffer_pool_load_now() |
| 91 | and it should return immediately because the whole MySQL is frozen during |
| 92 | its execution. */ |
| 93 | void |
| 94 | buf_load_start() |
| 95 | /*============*/ |
| 96 | { |
| 97 | buf_load_should_start = true; |
| 98 | os_event_set(srv_buf_dump_event); |
| 99 | } |
| 100 | |
| 101 | /*****************************************************************//** |
| 102 | Sets the global variable that feeds MySQL's innodb_buffer_pool_dump_status |
| 103 | to the specified string. The format and the following parameters are the |
| 104 | same as the ones used for printf(3). The value of this variable can be |
| 105 | retrieved by: |
| 106 | SELECT variable_value FROM information_schema.global_status WHERE |
| 107 | variable_name = 'INNODB_BUFFER_POOL_DUMP_STATUS'; |
| 108 | or by: |
| 109 | SHOW STATUS LIKE 'innodb_buffer_pool_dump_status'; */ |
| 110 | static MY_ATTRIBUTE((nonnull, format(printf, 2, 3))) |
| 111 | void |
| 112 | buf_dump_status( |
| 113 | /*============*/ |
| 114 | enum status_severity severity,/*!< in: status severity */ |
| 115 | const char* fmt, /*!< in: format */ |
| 116 | ...) /*!< in: extra parameters according |
| 117 | to fmt */ |
| 118 | { |
| 119 | va_list ap; |
| 120 | |
| 121 | va_start(ap, fmt); |
| 122 | |
| 123 | vsnprintf( |
| 124 | export_vars.innodb_buffer_pool_dump_status, |
| 125 | sizeof(export_vars.innodb_buffer_pool_dump_status), |
| 126 | fmt, ap); |
| 127 | |
| 128 | switch (severity) { |
| 129 | case STATUS_INFO: |
| 130 | ib::info() << export_vars.innodb_buffer_pool_dump_status; |
| 131 | break; |
| 132 | |
| 133 | case STATUS_ERR: |
| 134 | ib::error() << export_vars.innodb_buffer_pool_dump_status; |
| 135 | break; |
| 136 | } |
| 137 | |
| 138 | va_end(ap); |
| 139 | } |
| 140 | |
| 141 | /*****************************************************************//** |
| 142 | Sets the global variable that feeds MySQL's innodb_buffer_pool_load_status |
| 143 | to the specified string. The format and the following parameters are the |
| 144 | same as the ones used for printf(3). The value of this variable can be |
| 145 | retrieved by: |
| 146 | SELECT variable_value FROM information_schema.global_status WHERE |
| 147 | variable_name = 'INNODB_BUFFER_POOL_LOAD_STATUS'; |
| 148 | or by: |
| 149 | SHOW STATUS LIKE 'innodb_buffer_pool_load_status'; */ |
| 150 | static MY_ATTRIBUTE((nonnull, format(printf, 2, 3))) |
| 151 | void |
| 152 | buf_load_status( |
| 153 | /*============*/ |
| 154 | enum status_severity severity,/*!< in: status severity */ |
| 155 | const char* fmt, /*!< in: format */ |
| 156 | ...) /*!< in: extra parameters according to fmt */ |
| 157 | { |
| 158 | va_list ap; |
| 159 | |
| 160 | va_start(ap, fmt); |
| 161 | |
| 162 | vsnprintf( |
| 163 | export_vars.innodb_buffer_pool_load_status, |
| 164 | sizeof(export_vars.innodb_buffer_pool_load_status), |
| 165 | fmt, ap); |
| 166 | |
| 167 | switch (severity) { |
| 168 | case STATUS_INFO: |
| 169 | ib::info() << export_vars.innodb_buffer_pool_load_status; |
| 170 | break; |
| 171 | |
| 172 | case STATUS_ERR: |
| 173 | ib::error() << export_vars.innodb_buffer_pool_load_status; |
| 174 | break; |
| 175 | } |
| 176 | |
| 177 | va_end(ap); |
| 178 | } |
| 179 | |
| 180 | /** Returns the directory path where the buffer pool dump file will be created. |
| 181 | @return directory path */ |
| 182 | static |
| 183 | const char* |
| 184 | get_buf_dump_dir() |
| 185 | { |
| 186 | const char* dump_dir; |
| 187 | |
| 188 | /* The dump file should be created in the default data directory if |
| 189 | innodb_data_home_dir is set as an empty string. */ |
| 190 | if (strcmp(srv_data_home, "" ) == 0) { |
| 191 | dump_dir = fil_path_to_mysql_datadir; |
| 192 | } else { |
| 193 | dump_dir = srv_data_home; |
| 194 | } |
| 195 | |
| 196 | return(dump_dir); |
| 197 | } |
| 198 | |
| 199 | /** Generate the path to the buffer pool dump/load file. |
| 200 | @param[out] path generated path |
| 201 | @param[in] path_size size of 'path', used as in snprintf(3). */ |
| 202 | static |
| 203 | void |
| 204 | buf_dump_generate_path( |
| 205 | char* path, |
| 206 | size_t path_size) |
| 207 | { |
| 208 | char buf[FN_REFLEN]; |
| 209 | |
| 210 | snprintf(buf, sizeof(buf), "%s%c%s" , get_buf_dump_dir(), |
| 211 | OS_PATH_SEPARATOR, srv_buf_dump_filename); |
| 212 | |
| 213 | os_file_type_t type; |
| 214 | bool exists = false; |
| 215 | bool ret; |
| 216 | |
| 217 | ret = os_file_status(buf, &exists, &type); |
| 218 | |
| 219 | /* For realpath() to succeed the file must exist. */ |
| 220 | |
| 221 | if (ret && exists) { |
| 222 | /* my_realpath() assumes the destination buffer is big enough |
| 223 | to hold FN_REFLEN bytes. */ |
| 224 | ut_a(path_size >= FN_REFLEN); |
| 225 | |
| 226 | my_realpath(path, buf, 0); |
| 227 | } else { |
| 228 | /* If it does not exist, then resolve only srv_data_home |
| 229 | and append srv_buf_dump_filename to it. */ |
| 230 | char srv_data_home_full[FN_REFLEN]; |
| 231 | |
| 232 | my_realpath(srv_data_home_full, get_buf_dump_dir(), 0); |
| 233 | |
| 234 | if (srv_data_home_full[strlen(srv_data_home_full) - 1] |
| 235 | == OS_PATH_SEPARATOR) { |
| 236 | |
| 237 | snprintf(path, path_size, "%s%s" , |
| 238 | srv_data_home_full, |
| 239 | srv_buf_dump_filename); |
| 240 | } else { |
| 241 | snprintf(path, path_size, "%s%c%s" , |
| 242 | srv_data_home_full, |
| 243 | OS_PATH_SEPARATOR, |
| 244 | srv_buf_dump_filename); |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | /*****************************************************************//** |
| 250 | Perform a buffer pool dump into the file specified by |
| 251 | innodb_buffer_pool_filename. If any errors occur then the value of |
| 252 | innodb_buffer_pool_dump_status will be set accordingly, see buf_dump_status(). |
| 253 | The dump filename can be specified by (relative to srv_data_home): |
| 254 | SET GLOBAL innodb_buffer_pool_filename='filename'; */ |
| 255 | static |
| 256 | void |
| 257 | buf_dump( |
| 258 | /*=====*/ |
| 259 | ibool obey_shutdown) /*!< in: quit if we are in a shutting down |
| 260 | state */ |
| 261 | { |
| 262 | #define SHOULD_QUIT() (SHUTTING_DOWN() && obey_shutdown) |
| 263 | |
| 264 | char full_filename[OS_FILE_MAX_PATH]; |
| 265 | char tmp_filename[OS_FILE_MAX_PATH]; |
| 266 | char now[32]; |
| 267 | FILE* f; |
| 268 | ulint i; |
| 269 | int ret; |
| 270 | |
| 271 | buf_dump_generate_path(full_filename, sizeof(full_filename)); |
| 272 | |
| 273 | snprintf(tmp_filename, sizeof(tmp_filename), |
| 274 | "%s.incomplete" , full_filename); |
| 275 | |
| 276 | buf_dump_status(STATUS_INFO, "Dumping buffer pool(s) to %s" , |
| 277 | full_filename); |
| 278 | |
| 279 | #if defined(__GLIBC__) || defined(__WIN__) || O_CLOEXEC == 0 |
| 280 | f = fopen(tmp_filename, "w" STR_O_CLOEXEC); |
| 281 | #else |
| 282 | { |
| 283 | int fd; |
| 284 | fd = open(tmp_filename, O_CREAT | O_TRUNC | O_CLOEXEC | O_WRONLY, 0640); |
| 285 | if (fd >= 0) { |
| 286 | f = fdopen(fd, "w" ); |
| 287 | } |
| 288 | else { |
| 289 | f = NULL; |
| 290 | } |
| 291 | } |
| 292 | #endif |
| 293 | if (f == NULL) { |
| 294 | buf_dump_status(STATUS_ERR, |
| 295 | "Cannot open '%s' for writing: %s" , |
| 296 | tmp_filename, strerror(errno)); |
| 297 | return; |
| 298 | } |
| 299 | /* else */ |
| 300 | |
| 301 | /* walk through each buffer pool */ |
| 302 | for (i = 0; i < srv_buf_pool_instances && !SHOULD_QUIT(); i++) { |
| 303 | buf_pool_t* buf_pool; |
| 304 | const buf_page_t* bpage; |
| 305 | buf_dump_t* dump; |
| 306 | ulint n_pages; |
| 307 | ulint j; |
| 308 | |
| 309 | buf_pool = buf_pool_from_array(i); |
| 310 | |
| 311 | /* obtain buf_pool mutex before allocate, since |
| 312 | UT_LIST_GET_LEN(buf_pool->LRU) could change */ |
| 313 | buf_pool_mutex_enter(buf_pool); |
| 314 | |
| 315 | n_pages = UT_LIST_GET_LEN(buf_pool->LRU); |
| 316 | |
| 317 | /* skip empty buffer pools */ |
| 318 | if (n_pages == 0) { |
| 319 | buf_pool_mutex_exit(buf_pool); |
| 320 | continue; |
| 321 | } |
| 322 | |
| 323 | if (srv_buf_pool_dump_pct != 100) { |
| 324 | ulint t_pages; |
| 325 | |
| 326 | ut_ad(srv_buf_pool_dump_pct < 100); |
| 327 | |
| 328 | /* limit the number of total pages dumped to X% of the |
| 329 | * total number of pages */ |
| 330 | t_pages = buf_pool->curr_size |
| 331 | * srv_buf_pool_dump_pct / 100; |
| 332 | if (n_pages > t_pages) { |
| 333 | buf_dump_status(STATUS_INFO, |
| 334 | "Instance " ULINTPF |
| 335 | ", restricted to " ULINTPF |
| 336 | " pages due to " |
| 337 | "innodb_buf_pool_dump_pct=%lu" , |
| 338 | i, t_pages, |
| 339 | srv_buf_pool_dump_pct); |
| 340 | n_pages = t_pages; |
| 341 | } |
| 342 | |
| 343 | if (n_pages == 0) { |
| 344 | n_pages = 1; |
| 345 | } |
| 346 | } |
| 347 | |
| 348 | dump = static_cast<buf_dump_t*>(ut_malloc_nokey( |
| 349 | n_pages * sizeof(*dump))); |
| 350 | |
| 351 | if (dump == NULL) { |
| 352 | buf_pool_mutex_exit(buf_pool); |
| 353 | fclose(f); |
| 354 | buf_dump_status(STATUS_ERR, |
| 355 | "Cannot allocate " ULINTPF " bytes: %s" , |
| 356 | (ulint) (n_pages * sizeof(*dump)), |
| 357 | strerror(errno)); |
| 358 | /* leave tmp_filename to exist */ |
| 359 | return; |
| 360 | } |
| 361 | |
| 362 | for (bpage = UT_LIST_GET_FIRST(buf_pool->LRU), j = 0; |
| 363 | bpage != NULL && j < n_pages; |
| 364 | bpage = UT_LIST_GET_NEXT(LRU, bpage)) { |
| 365 | |
| 366 | ut_a(buf_page_in_file(bpage)); |
| 367 | if (bpage->id.space() >= SRV_LOG_SPACE_FIRST_ID) { |
| 368 | /* Ignore the innodb_temporary tablespace. */ |
| 369 | continue; |
| 370 | } |
| 371 | |
| 372 | dump[j++] = BUF_DUMP_CREATE(bpage->id.space(), |
| 373 | bpage->id.page_no()); |
| 374 | } |
| 375 | |
| 376 | buf_pool_mutex_exit(buf_pool); |
| 377 | |
| 378 | ut_a(j <= n_pages); |
| 379 | n_pages = j; |
| 380 | |
| 381 | for (j = 0; j < n_pages && !SHOULD_QUIT(); j++) { |
| 382 | ret = fprintf(f, ULINTPF "," ULINTPF "\n" , |
| 383 | BUF_DUMP_SPACE(dump[j]), |
| 384 | BUF_DUMP_PAGE(dump[j])); |
| 385 | if (ret < 0) { |
| 386 | ut_free(dump); |
| 387 | fclose(f); |
| 388 | buf_dump_status(STATUS_ERR, |
| 389 | "Cannot write to '%s': %s" , |
| 390 | tmp_filename, strerror(errno)); |
| 391 | /* leave tmp_filename to exist */ |
| 392 | return; |
| 393 | } |
| 394 | if ( (j % 1024) == 0) { |
| 395 | service_manager_extend_timeout(INNODB_EXTEND_TIMEOUT_INTERVAL, |
| 396 | "Dumping buffer pool " |
| 397 | ULINTPF "/" ULINTPF ", " |
| 398 | "page " ULINTPF "/" ULINTPF, |
| 399 | i + 1, srv_buf_pool_instances, |
| 400 | j + 1, n_pages); |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | ut_free(dump); |
| 405 | } |
| 406 | |
| 407 | ret = fclose(f); |
| 408 | if (ret != 0) { |
| 409 | buf_dump_status(STATUS_ERR, |
| 410 | "Cannot close '%s': %s" , |
| 411 | tmp_filename, strerror(errno)); |
| 412 | return; |
| 413 | } |
| 414 | /* else */ |
| 415 | |
| 416 | ret = unlink(full_filename); |
| 417 | if (ret != 0 && errno != ENOENT) { |
| 418 | buf_dump_status(STATUS_ERR, |
| 419 | "Cannot delete '%s': %s" , |
| 420 | full_filename, strerror(errno)); |
| 421 | /* leave tmp_filename to exist */ |
| 422 | return; |
| 423 | } |
| 424 | /* else */ |
| 425 | |
| 426 | ret = rename(tmp_filename, full_filename); |
| 427 | if (ret != 0) { |
| 428 | buf_dump_status(STATUS_ERR, |
| 429 | "Cannot rename '%s' to '%s': %s" , |
| 430 | tmp_filename, full_filename, |
| 431 | strerror(errno)); |
| 432 | /* leave tmp_filename to exist */ |
| 433 | return; |
| 434 | } |
| 435 | /* else */ |
| 436 | |
| 437 | /* success */ |
| 438 | |
| 439 | ut_sprintf_timestamp(now); |
| 440 | |
| 441 | buf_dump_status(STATUS_INFO, |
| 442 | "Buffer pool(s) dump completed at %s" , now); |
| 443 | |
| 444 | /* Though dumping doesn't related to an incomplete load, |
| 445 | we reset this to 0 here to indicate that a shutdown can also perform |
| 446 | a dump */ |
| 447 | export_vars.innodb_buffer_pool_load_incomplete = 0; |
| 448 | } |
| 449 | |
| 450 | /*****************************************************************//** |
| 451 | Artificially delay the buffer pool loading if necessary. The idea of |
| 452 | this function is to prevent hogging the server with IO and slowing down |
| 453 | too much normal client queries. */ |
| 454 | UNIV_INLINE |
| 455 | void |
| 456 | buf_load_throttle_if_needed( |
| 457 | /*========================*/ |
| 458 | ulint* last_check_time, /*!< in/out: milliseconds since epoch |
| 459 | of the last time we did check if |
| 460 | throttling is needed, we do the check |
| 461 | every srv_io_capacity IO ops. */ |
| 462 | ulint* last_activity_count, |
| 463 | ulint n_io) /*!< in: number of IO ops done since |
| 464 | buffer pool load has started */ |
| 465 | { |
| 466 | if (n_io % srv_io_capacity < srv_io_capacity - 1) { |
| 467 | return; |
| 468 | } |
| 469 | |
| 470 | if (*last_check_time == 0 || *last_activity_count == 0) { |
| 471 | *last_check_time = ut_time_ms(); |
| 472 | *last_activity_count = srv_get_activity_count(); |
| 473 | return; |
| 474 | } |
| 475 | |
| 476 | /* srv_io_capacity IO operations have been performed by buffer pool |
| 477 | load since the last time we were here. */ |
| 478 | |
| 479 | /* If no other activity, then keep going without any delay. */ |
| 480 | if (srv_get_activity_count() == *last_activity_count) { |
| 481 | return; |
| 482 | } |
| 483 | |
| 484 | /* There has been other activity, throttle. */ |
| 485 | |
| 486 | ulint now = ut_time_ms(); |
| 487 | ulint elapsed_time = now - *last_check_time; |
| 488 | |
| 489 | /* Notice that elapsed_time is not the time for the last |
| 490 | srv_io_capacity IO operations performed by BP load. It is the |
| 491 | time elapsed since the last time we detected that there has been |
| 492 | other activity. This has a small and acceptable deficiency, e.g.: |
| 493 | 1. BP load runs and there is no other activity. |
| 494 | 2. Other activity occurs, we run N IO operations after that and |
| 495 | enter here (where 0 <= N < srv_io_capacity). |
| 496 | 3. last_check_time is very old and we do not sleep at this time, but |
| 497 | only update last_check_time and last_activity_count. |
| 498 | 4. We run srv_io_capacity more IO operations and call this function |
| 499 | again. |
| 500 | 5. There has been more other activity and thus we enter here. |
| 501 | 6. Now last_check_time is recent and we sleep if necessary to prevent |
| 502 | more than srv_io_capacity IO operations per second. |
| 503 | The deficiency is that we could have slept at 3., but for this we |
| 504 | would have to update last_check_time before the |
| 505 | "cur_activity_count == *last_activity_count" check and calling |
| 506 | ut_time_ms() that often may turn out to be too expensive. */ |
| 507 | |
| 508 | if (elapsed_time < 1000 /* 1 sec (1000 milli secs) */) { |
| 509 | os_thread_sleep((1000 - elapsed_time) * 1000 /* micro secs */); |
| 510 | } |
| 511 | |
| 512 | *last_check_time = ut_time_ms(); |
| 513 | *last_activity_count = srv_get_activity_count(); |
| 514 | } |
| 515 | |
| 516 | /*****************************************************************//** |
| 517 | Perform a buffer pool load from the file specified by |
| 518 | innodb_buffer_pool_filename. If any errors occur then the value of |
| 519 | innodb_buffer_pool_load_status will be set accordingly, see buf_load_status(). |
| 520 | The dump filename can be specified by (relative to srv_data_home): |
| 521 | SET GLOBAL innodb_buffer_pool_filename='filename'; */ |
| 522 | static |
| 523 | void |
| 524 | buf_load() |
| 525 | /*======*/ |
| 526 | { |
| 527 | char full_filename[OS_FILE_MAX_PATH]; |
| 528 | char now[32]; |
| 529 | FILE* f; |
| 530 | buf_dump_t* dump; |
| 531 | ulint dump_n; |
| 532 | ulint total_buffer_pools_pages; |
| 533 | ulint i; |
| 534 | ulint space_id; |
| 535 | ulint page_no; |
| 536 | int fscanf_ret; |
| 537 | |
| 538 | /* Ignore any leftovers from before */ |
| 539 | buf_load_abort_flag = FALSE; |
| 540 | |
| 541 | buf_dump_generate_path(full_filename, sizeof(full_filename)); |
| 542 | |
| 543 | buf_load_status(STATUS_INFO, |
| 544 | "Loading buffer pool(s) from %s" , full_filename); |
| 545 | |
| 546 | f = fopen(full_filename, "r" STR_O_CLOEXEC); |
| 547 | if (f == NULL) { |
| 548 | buf_load_status(STATUS_INFO, |
| 549 | "Cannot open '%s' for reading: %s" , |
| 550 | full_filename, strerror(errno)); |
| 551 | return; |
| 552 | } |
| 553 | /* else */ |
| 554 | |
| 555 | /* First scan the file to estimate how many entries are in it. |
| 556 | This file is tiny (approx 500KB per 1GB buffer pool), reading it |
| 557 | two times is fine. */ |
| 558 | dump_n = 0; |
| 559 | while (fscanf(f, ULINTPF "," ULINTPF, &space_id, &page_no) == 2 |
| 560 | && !SHUTTING_DOWN()) { |
| 561 | dump_n++; |
| 562 | } |
| 563 | |
| 564 | if (!SHUTTING_DOWN() && !feof(f)) { |
| 565 | /* fscanf() returned != 2 */ |
| 566 | const char* what; |
| 567 | if (ferror(f)) { |
| 568 | what = "reading" ; |
| 569 | } else { |
| 570 | what = "parsing" ; |
| 571 | } |
| 572 | fclose(f); |
| 573 | buf_load_status(STATUS_ERR, "Error %s '%s'," |
| 574 | " unable to load buffer pool (stage 1)" , |
| 575 | what, full_filename); |
| 576 | return; |
| 577 | } |
| 578 | |
| 579 | /* If dump is larger than the buffer pool(s), then we ignore the |
| 580 | extra trailing. This could happen if a dump is made, then buffer |
| 581 | pool is shrunk and then load is attempted. */ |
| 582 | total_buffer_pools_pages = buf_pool_get_n_pages() |
| 583 | * srv_buf_pool_instances; |
| 584 | if (dump_n > total_buffer_pools_pages) { |
| 585 | dump_n = total_buffer_pools_pages; |
| 586 | } |
| 587 | |
| 588 | if(dump_n != 0) { |
| 589 | dump = static_cast<buf_dump_t*>(ut_malloc_nokey( |
| 590 | dump_n * sizeof(*dump))); |
| 591 | } else { |
| 592 | fclose(f); |
| 593 | ut_sprintf_timestamp(now); |
| 594 | buf_load_status(STATUS_INFO, |
| 595 | "Buffer pool(s) load completed at %s" |
| 596 | " (%s was empty)" , now, full_filename); |
| 597 | return; |
| 598 | } |
| 599 | |
| 600 | if (dump == NULL) { |
| 601 | fclose(f); |
| 602 | buf_load_status(STATUS_ERR, |
| 603 | "Cannot allocate " ULINTPF " bytes: %s" , |
| 604 | dump_n * sizeof(*dump), |
| 605 | strerror(errno)); |
| 606 | return; |
| 607 | } |
| 608 | |
| 609 | rewind(f); |
| 610 | |
| 611 | export_vars.innodb_buffer_pool_load_incomplete = 1; |
| 612 | |
| 613 | for (i = 0; i < dump_n && !SHUTTING_DOWN(); i++) { |
| 614 | fscanf_ret = fscanf(f, ULINTPF "," ULINTPF, |
| 615 | &space_id, &page_no); |
| 616 | |
| 617 | if (fscanf_ret != 2) { |
| 618 | if (feof(f)) { |
| 619 | break; |
| 620 | } |
| 621 | /* else */ |
| 622 | |
| 623 | ut_free(dump); |
| 624 | fclose(f); |
| 625 | buf_load_status(STATUS_ERR, |
| 626 | "Error parsing '%s', unable" |
| 627 | " to load buffer pool (stage 2)" , |
| 628 | full_filename); |
| 629 | return; |
| 630 | } |
| 631 | |
| 632 | if (space_id > ULINT32_MASK || page_no > ULINT32_MASK) { |
| 633 | ut_free(dump); |
| 634 | fclose(f); |
| 635 | buf_load_status(STATUS_ERR, |
| 636 | "Error parsing '%s': bogus" |
| 637 | " space,page " ULINTPF "," ULINTPF |
| 638 | " at line " ULINTPF "," |
| 639 | " unable to load buffer pool" , |
| 640 | full_filename, |
| 641 | space_id, page_no, |
| 642 | i); |
| 643 | return; |
| 644 | } |
| 645 | |
| 646 | dump[i] = BUF_DUMP_CREATE(space_id, page_no); |
| 647 | } |
| 648 | |
| 649 | /* Set dump_n to the actual number of initialized elements, |
| 650 | i could be smaller than dump_n here if the file got truncated after |
| 651 | we read it the first time. */ |
| 652 | dump_n = i; |
| 653 | |
| 654 | fclose(f); |
| 655 | |
| 656 | if (dump_n == 0) { |
| 657 | ut_free(dump); |
| 658 | ut_sprintf_timestamp(now); |
| 659 | buf_load_status(STATUS_INFO, |
| 660 | "Buffer pool(s) load completed at %s" |
| 661 | " (%s was empty or had errors)" , now, full_filename); |
| 662 | return; |
| 663 | } |
| 664 | |
| 665 | if (!SHUTTING_DOWN()) { |
| 666 | std::sort(dump, dump + dump_n); |
| 667 | } |
| 668 | |
| 669 | ulint last_check_time = 0; |
| 670 | ulint last_activity_cnt = 0; |
| 671 | |
| 672 | /* Avoid calling the expensive fil_space_acquire_silent() for each |
| 673 | page within the same tablespace. dump[] is sorted by (space, page), |
| 674 | so all pages from a given tablespace are consecutive. */ |
| 675 | ulint cur_space_id = BUF_DUMP_SPACE(dump[0]); |
| 676 | fil_space_t* space = fil_space_acquire_silent(cur_space_id); |
| 677 | page_size_t page_size(space ? space->flags : 0); |
| 678 | |
| 679 | /* JAN: TODO: MySQL 5.7 PSI |
| 680 | #ifdef HAVE_PSI_STAGE_INTERFACE |
| 681 | PSI_stage_progress* pfs_stage_progress |
| 682 | = mysql_set_stage(srv_stage_buffer_pool_load.m_key); |
| 683 | #endif*/ /* HAVE_PSI_STAGE_INTERFACE */ |
| 684 | /* |
| 685 | mysql_stage_set_work_estimated(pfs_stage_progress, dump_n); |
| 686 | mysql_stage_set_work_completed(pfs_stage_progress, 0); |
| 687 | */ |
| 688 | |
| 689 | for (i = 0; i < dump_n && !SHUTTING_DOWN(); i++) { |
| 690 | |
| 691 | /* space_id for this iteration of the loop */ |
| 692 | const ulint this_space_id = BUF_DUMP_SPACE(dump[i]); |
| 693 | |
| 694 | if (this_space_id >= SRV_LOG_SPACE_FIRST_ID) { |
| 695 | /* Ignore the innodb_temporary tablespace. */ |
| 696 | continue; |
| 697 | } |
| 698 | |
| 699 | if (this_space_id != cur_space_id) { |
| 700 | if (space != NULL) { |
| 701 | space->release(); |
| 702 | } |
| 703 | |
| 704 | cur_space_id = this_space_id; |
| 705 | space = fil_space_acquire_silent(cur_space_id); |
| 706 | |
| 707 | if (space != NULL) { |
| 708 | const page_size_t cur_page_size( |
| 709 | space->flags); |
| 710 | page_size.copy_from(cur_page_size); |
| 711 | } |
| 712 | } |
| 713 | |
| 714 | /* JAN: TODO: As we use background page read below, |
| 715 | if tablespace is encrypted we cant use it. */ |
| 716 | if (space == NULL || |
| 717 | (space && space->crypt_data && |
| 718 | space->crypt_data->encryption != FIL_ENCRYPTION_OFF && |
| 719 | space->crypt_data->type != CRYPT_SCHEME_UNENCRYPTED)) { |
| 720 | continue; |
| 721 | } |
| 722 | |
| 723 | buf_read_page_background( |
| 724 | page_id_t(this_space_id, BUF_DUMP_PAGE(dump[i])), |
| 725 | page_size, true); |
| 726 | |
| 727 | if (i % 64 == 63) { |
| 728 | os_aio_simulated_wake_handler_threads(); |
| 729 | } |
| 730 | |
| 731 | if (buf_load_abort_flag) { |
| 732 | if (space != NULL) { |
| 733 | space->release(); |
| 734 | } |
| 735 | buf_load_abort_flag = FALSE; |
| 736 | ut_free(dump); |
| 737 | buf_load_status( |
| 738 | STATUS_INFO, |
| 739 | "Buffer pool(s) load aborted on request" ); |
| 740 | /* Premature end, set estimated = completed = i and |
| 741 | end the current stage event. */ |
| 742 | /* |
| 743 | mysql_stage_set_work_estimated(pfs_stage_progress, i); |
| 744 | mysql_stage_set_work_completed(pfs_stage_progress, |
| 745 | i); |
| 746 | */ |
| 747 | #ifdef HAVE_PSI_STAGE_INTERFACE |
| 748 | /* mysql_end_stage(); */ |
| 749 | #endif /* HAVE_PSI_STAGE_INTERFACE */ |
| 750 | return; |
| 751 | } |
| 752 | |
| 753 | buf_load_throttle_if_needed( |
| 754 | &last_check_time, &last_activity_cnt, i); |
| 755 | |
| 756 | #ifdef UNIV_DEBUG |
| 757 | if ((i+1) >= srv_buf_pool_load_pages_abort) { |
| 758 | buf_load_abort_flag = 1; |
| 759 | } |
| 760 | #endif |
| 761 | } |
| 762 | |
| 763 | if (space != NULL) { |
| 764 | space->release(); |
| 765 | } |
| 766 | |
| 767 | ut_free(dump); |
| 768 | |
| 769 | ut_sprintf_timestamp(now); |
| 770 | |
| 771 | if (i == dump_n) { |
| 772 | buf_load_status(STATUS_INFO, |
| 773 | "Buffer pool(s) load completed at %s" , now); |
| 774 | export_vars.innodb_buffer_pool_load_incomplete = 0; |
| 775 | } else if (!buf_load_abort_flag) { |
| 776 | buf_load_status(STATUS_INFO, |
| 777 | "Buffer pool(s) load aborted due to user instigated abort at %s" , |
| 778 | now); |
| 779 | /* intentionally don't reset innodb_buffer_pool_load_incomplete |
| 780 | as we don't want a shutdown to save the buffer pool */ |
| 781 | } else { |
| 782 | buf_load_status(STATUS_INFO, |
| 783 | "Buffer pool(s) load aborted due to shutdown at %s" , |
| 784 | now); |
| 785 | /* intentionally don't reset innodb_buffer_pool_load_incomplete |
| 786 | as we want to abort without saving the buffer pool */ |
| 787 | } |
| 788 | |
| 789 | /* Make sure that estimated = completed when we end. */ |
| 790 | /* mysql_stage_set_work_completed(pfs_stage_progress, dump_n); */ |
| 791 | /* End the stage progress event. */ |
| 792 | #ifdef HAVE_PSI_STAGE_INTERFACE |
| 793 | /* mysql_end_stage(); */ |
| 794 | #endif /* HAVE_PSI_STAGE_INTERFACE */ |
| 795 | } |
| 796 | |
| 797 | /*****************************************************************//** |
| 798 | Aborts a currently running buffer pool load. This function is called by |
| 799 | MySQL code via buffer_pool_load_abort() and it should return immediately |
| 800 | because the whole MySQL is frozen during its execution. */ |
| 801 | void |
| 802 | buf_load_abort() |
| 803 | /*============*/ |
| 804 | { |
| 805 | buf_load_abort_flag = TRUE; |
| 806 | } |
| 807 | |
| 808 | /*****************************************************************//** |
| 809 | This is the main thread for buffer pool dump/load. It waits for an |
| 810 | event and when waked up either performs a dump or load and sleeps |
| 811 | again. |
| 812 | @return this function does not return, it calls os_thread_exit() */ |
| 813 | extern "C" |
| 814 | os_thread_ret_t |
| 815 | DECLARE_THREAD(buf_dump_thread)(void*) |
| 816 | { |
| 817 | my_thread_init(); |
| 818 | ut_ad(!srv_read_only_mode); |
| 819 | /* JAN: TODO: MySQL 5.7 PSI |
| 820 | #ifdef UNIV_PFS_THREAD |
| 821 | pfs_register_thread(buf_dump_thread_key); |
| 822 | #endif */ /* UNIV_PFS_THREAD */ |
| 823 | |
| 824 | if (srv_buffer_pool_load_at_startup) { |
| 825 | |
| 826 | #ifdef WITH_WSREP |
| 827 | if (!wsrep_recovery) { |
| 828 | #endif /* WITH_WSREP */ |
| 829 | buf_load(); |
| 830 | #ifdef WITH_WSREP |
| 831 | } |
| 832 | #endif /* WITH_WSREP */ |
| 833 | } |
| 834 | |
| 835 | while (!SHUTTING_DOWN()) { |
| 836 | |
| 837 | os_event_wait(srv_buf_dump_event); |
| 838 | |
| 839 | if (buf_dump_should_start) { |
| 840 | buf_dump_should_start = false; |
| 841 | buf_dump(TRUE /* quit on shutdown */); |
| 842 | } |
| 843 | |
| 844 | if (buf_load_should_start) { |
| 845 | buf_load_should_start = false; |
| 846 | buf_load(); |
| 847 | } |
| 848 | |
| 849 | if (buf_dump_should_start || buf_load_should_start) { |
| 850 | continue; |
| 851 | } |
| 852 | os_event_reset(srv_buf_dump_event); |
| 853 | } |
| 854 | |
| 855 | if (srv_buffer_pool_dump_at_shutdown && srv_fast_shutdown != 2) { |
| 856 | if (export_vars.innodb_buffer_pool_load_incomplete) { |
| 857 | buf_dump_status(STATUS_INFO, |
| 858 | "Dumping of buffer pool not started" |
| 859 | " as load was incomplete" ); |
| 860 | #ifdef WITH_WSREP |
| 861 | } else if (wsrep_recovery) { |
| 862 | #endif /* WITH_WSREP */ |
| 863 | } else { |
| 864 | buf_dump(FALSE/* do complete dump at shutdown */); |
| 865 | } |
| 866 | } |
| 867 | |
| 868 | srv_buf_dump_thread_active = false; |
| 869 | |
| 870 | my_thread_end(); |
| 871 | /* We count the number of threads in os_thread_exit(). A created |
| 872 | thread should always use that to exit and not use return() to exit. */ |
| 873 | os_thread_exit(); |
| 874 | |
| 875 | OS_THREAD_DUMMY_RETURN; |
| 876 | } |
| 877 | |