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