===== File: Docs/replication/binlog.md ===== 1 : : +# New binlog implementation 2 : : + 3 : : +This document describes the new binlog implementation that is enabled using 4 : : +the `--binlog-storage-engine` option. 5 : : + 6 : : +The new binlog uses a more efficient on-disk format that is integrated with 7 : : +the InnoDB write-ahead log. This provides two main benefits: 8 : : + 9 : : +1. The binlog will always be recovered into a consistent state after a crash. This makes it possible to use the options `--innodb-flush-log-at-trx-commit=0` or `--innodb-flush-log-at-trx-commit=2`, which can give a huge performance improvement depending on disk speed and transaction concurrency. 10 : : +2. When using the option `--innodb-flush-log-at-trx-commit=1`, commits are more efficient since there is no expensive two-phase commit between the binlog and the InnoDB storage engine. 11 : : + 12 : : +## Using the new binlog 13 : : + 14 : : +To use the new binlog, configure the server with the following two options: 15 : : +1. `log_bin` 16 : : +2. `binlog_storage_engine=innodb` 17 : : +Note that the `log_bin` option must be specified like that, without any argument; the option is not an on-off-switch. 18 : : + 19 : : +Optionally, the directory in which to store binlog files can be specified with `binlog_directory=`. By default, the data directory is used. 20 : : + 21 : : +Note that using the new binlog is mutually exclusive with the traditional binlog format. Configuring an existing server to use the new binlog format will effectively ignore any old binlog files. This limitation may be relaxed in a future version of MariaDB. 22 : : + 23 : : +## Replicating with the new binlog 24 : : + 25 : : +Configuration of replication from a master using the new binlog is done in the usual way. Slaves must be configured to use Global Transaction ID (GTID) to connect to the master (this is the default). The old filename/offset-based replication position is not available when using the new binlog implementation on the master. 26 : : + 27 : : +## Working with the binlog files 28 : : + 29 : : +The binlog files will be written to the data directory (or to the directory configured with `--binlog-directory`). The files are named `binlog-000000.ibb`, `binlog-000001.ibb`, ... and so on. 30 : : + 31 : : +The size of each binlog file is determined by the value of `max_binlog_size` (by default 1 GB). The binlog files are pre-allocated, so they will always have the configured size, with the last one or two files being possibly partially empty. The exception is when the command `FLUSH BINARY LOGS` is used; then the last active binlog file will be truncated to the used part of it, and binlog writes will continue in the following file. 32 : : + 33 : : +The list of current binlog files can be obtained with the command `SHOW BINLOG EVENTS`. Note that there is no binlog index file (`.index`) like with the traditional binlog format, nor are there any GTID index files (`.idx`) or GTID state (`.state`) file (`.state`). 34 : : + 35 : : +Instead of the GTID index and state files, the new binlog periodically writes GTID *state records* into the binlog containing the equivalent information. When a slave connects to the master, as well as when the server starts up, the binlog will be scanned from the last GTID state record to find or recover the correct GTID position. The `--innodb-binlog-state-interval` configuration option controls the interval (in bytes) between writing a state record. Thus, this option can be increased to reduce the overhead of state records, or decreased to speed up finding the initial GTID position at slave connect. The overhead however is small either way, and normally there is little reason to change the default. 36 : : + 37 : : +Binlog files can be purged (removed) automatically after a configured time or disk space usage, provided they are no longer needed by active replication slaves or for possible crash recovery. This is configured using the options `binlog_expire_log_seconds`, `binlog_expire_log_days`, `max_binlog_total_size`, and `slave_connections_needed_for_purge`. 38 : : + 39 : : +The contents of binlog files can be inspected in two ways: 40 : : +1. From within the server, using the command `SHOW BINLOG EVENTS`. 41 : : +2. Independent of the server, using the `mariadb-binlog` command-line program. 42 : : + 43 : : +Unlike in the traditional binlog format, one binlog event can be stored in multiple different binlog files, and different parts of individual events can be interleaved with one another in each file. The `mariadb-binlog` program will coalesce the different parts of each event as necessary, so the output of the program is a consistent, non-interleaved stream of events. To obtain a correct seequnce of events across multiple binlog files, all binlog files should be passed to the `mariadb-binlog` program at once in correct order; this ensures that events that cross file boundaries are included in the output exactly once. 44 : : + 45 : : +When using the `--start-position` and `--stop-position` options of `mariadb-binlog`, it is recommended to use GTID positions. The event file offsets used in the tranditional binlog format are not used in the new binlog, and will mostly be reported as zero. 46 : : + 47 : : +The `--binlog-checksum` option is no longer used with the new binlog implementation. The binlog files are always checksummed, with a CRC32 at the end of each page. To have checksum of the data sent on the network between the master and the slave (in addition to the normal TCP checksums), use the `MASTER_SSL` option for `CHANGE MASTER` to make the connection use SSL. 48 : : + 49 : : +## Using the new binlog with mariadb-backup 50 : : + 51 : : +The `mariadb-backup` program will by default back up the binlog files together with the rest of the server data. This fixes a long-standing limitation of the old binlog that it is missing from backups made with `mariadb-backup`. 52 : : + 53 : : +The binlog files are backed up in a transactionally consistent way, just like other InnoDB data. This means that a restored backup can be used to setup a new slave simply by using the `MASTER_DEMOTE_TO_SLAVE=1` option of `CHANGE MASTER`. 54 : : + 55 : : +The server being backed up is not blocked during the copy of the binlog files; only `RESET MASTER`, `PURGE BINARY LOGS` and `FLUSH BINARY LOGS` are blocked by default. This blocking can be disabled with the option `--no-lock` option. 56 : : + 57 : : +To omit the binlog files from the backup (ie. to save space in the backup when the binlog files are known to be not needed), use the `--skip-binlog` option on both the `mariadb-backup --backup` and `mariadb-backup --prepare` step. Note that when binlog files are omitted from the backup, the restored server will behave as if `RESET MASTER` was run on it just at the point of the backup. Also note that any transactions that were prepared, but not yet committed, at the time of the backup will be rolled back when the restored server starts up for the first time. 58 : : + 59 : : +## `FLUSH BINARY LOGS` 60 : : + 61 : : +Binlog files are pre-allocated for efficiency. When binlog file N is filled up, any remainder event data continues in file N+1, and and empty file N+2 is pre-allocated in the background. This means that binlog files are always exactly `--max-binlog-size` bytes long; and if the server restarts, binlog writing continues at the point reached before shutdown. 62 : : + 63 : : +The exception is when the `FLUSH BINARY LOGS` command is run. This pads the current binlog file up to the next page boundary, truncates the file, and switches to the next file. This can thus leave the binlog file smaller than `--max-binlog-size` (but always a multiple of the binlog page size). 64 : : + 65 : : +The `FLUSH BINARY LOGS DELETE_DOMAIN_ID=N` can be used to remove an old domain id from the `@@gtid_binlog_pos`. This requires that the domain is not in use in any existing binlog files; a combination of running `FLUSH BINARY LOGS` and `PURGE BINARY LOGS TO` can help ensure this. If the domain id N is already deleted, a warning is issues but the `FLUSH BINARY LOGS` operation is still run (this is for consistency, but is different from the old binlog implementation, where the `FLUSH` is skipped if the domain id was already deleted. 66 : : + 67 : : +## Using the new binlog with 3rd-party programs 68 : : + 69 : : +The new binlog uses a different on-disk format than the traditional binlog. The format of individual replication events is the same; however the files stored on disk are page-based, and each page has some encapsulation of event data to support splitting events in multiple pieces etc. 70 : : + 71 : : +This means that existing 3rd-party programs that read the binlog files directly will need to be modified to support the new format. Until then, such programs will require using the traditional binlog format. 72 : : + 73 : : +The protocol for reading binlog data from a running server (eg. for a connecting slave) is however mostly unchanged. This means existing programs that read binlog events from a running server may be able to function unmodified with the new binlog. Similarly, `mariadb-binlog` with the `--read-from-remote-server` option works as usual. 74 : : + 75 : : +A difference is that file offsets and file bondaries are no longer meaningful and no longer reported to the connecting client. There are no rotate events at the end of a file to specify the name of the following file, nor is there a new format description event at the start of each new file. Effectively, the binlog appears as a single unbroken stream of events to clients. The position from which to start receiving binlog events from the server should be specified using a GTID position; specifying a filename and file offset is not available. 76 : : + 77 : : +### Documentation of the binlog file format 78 : : + 79 : : +A binlog file consists of a sequence of pages. The page size is currently fixed at 16kByte. The size of a binlog file is set with the `--max-binlog-size` option. Each page has a CRC32 in the last 4 bytes; all remaining bytes are used for data. 80 : : + 81 : : +Numbers are stored in little-endian format. Some numbers are stored as compressed integers. A compressed integer consists of 1-9 bytes. The lower 3 bits determine the number of bytes used. The number of bytes is one more than the value in the lower 3 bits, except that a value of 7 means that 9 bytes are used. The value of the number stored is the little-endian value of the used bytes, right-shifted by 3. 82 : : + 83 : : +The first page in each binlog file is a file header page, with the following format: 84 : : +#. Offset 0: 4-byte MAGIC value 0x010dfefe to identify the file as a binlog file. 85 : : +#. Offset 4: The log-2 of the page size, currently fixed at 14 for a 16kByte page size. 86 : : +#. Offset 8: Major file version, currently 1. A new major version is not readable by older server versions. 87 : : +#. Offset 12: Minor file version, currently 0. New minor versions are backwards-compatible with older server versions. 88 : : +#. Offset 16: The file number (same as the number in the `binlog-NNNNNN.ibb` file name), for consistency check. 89 : : +#. Offset 24: The size of the file, in pages. 90 : : +#. Offset 32: The InnoDB LSN corresponding to the start of the file, used for crash recovery. 91 : : +#. Offset 40: The value of `--innodb-binlog-state-interval` used in the file. 92 : : +#. Offset 48: The file number of the earliest file into which this file may contain references into (such references occur when a large event group is split into multiple pieces, called out-of-band or oob records, and are used to locate all the pieces of event data from the final commit record). Used to prevent purging binlog files that contain data that may still be needed. 93 : : +#. Offset 56: The file number of the earliest file containing pending XA transactions that may still be active. 94 : : +#. Offset 64: Unused. 95 : : +#. Offset 512: Extra CRC32. This is used for future expansion to support configurable binlog page size. The header page can be read and checksummed as a 512-byte page, after which the real page size can be determined from the value at offset 4. 96 : : + 97 : : +Remaining pages in the file are data pages. Data is structured as a sequence of *records*; each record consists of one or more chunks. A page contains one or more chunks; a record can span multiple pages, but a chunk always fits within one page. Chunks are a minumum of 4 bytes long; any remaining 1-3 bytes of data in a page are filled with the byte 0xff. Unused bytes in a page are set to 0x00. 98 : : + 99 : : +A chunk consists of a *type byte*, two *length bytes* (little endian), and the *data bytes*. The length bytes count only the data bytes, so if the length bytes are 0x0001, then the total size of the chunk is 4 bytes. 100 : : + 101 : : +The high two bits of the type byte are used to collect chunks into records: 102 : : +#. Bit 7 is clear for the first chunk in a record, and set for all following chunks in the record. 103 : : +#. Bit 6 is set for the last chunk in a record, and clear for any prior chunks. 104 : : + 105 : : +These are the chunk types used: 106 : : + 107 : : +#. Type=0 (not a real type, 0 is an unused byte and denotes end-of-file in the current binlog file). 108 : : +#. Type=1: A commit record, containing binlog event data. First a compressed integer of the number of oob records referenced by the commit record, if any; then if non-zero, four more compressed integers of the file number and offset of the first and last such reference. This is followed by another similar 1 or 5 compressed integers, only used in the special case where transactional and non-transactional updates are mixed in a single event group. The remainder bytes are the payload data. 109 : : +#. Type=2: This is a GTID state record, written every `--innodb-binlog-state-interval` bytes. It consists of a sequence of compressed integers. The first is the number of GTIDs in the GTID state stored in the record. The second is one more than the earliest file number containing possibly still active XA transactions (used for crash recovery), or 0 for none. After this comes N*3 compressed integers, each representing a GTID in the GTID state. 110 : : +#. Type=3: This is an out-of-band (oob) data record. It is used to split large event groups into smaller pieces, organized as a forest of perfect binary trees. The record starts with 5 compressed integers: A node index (starts at 0 and increments for each oob record in an event group); the file number and offset of the left child oob node; and the file number and offset of the right child oob node. Remainder bytes are the payload event data. 111 : : +#. Type=4: This is a filler record, it is used to pad out the last page of a binlog file which is truncated due to `FLUSH BINARY LOGS`. 112 : : +#. Type=5: This is an XA PREPARE record, used for consistent crash recovery of user XA transactions. It starts with 1 byte counting the number of storage engines participating in the transaction. Then follows the XID (4 byte formatID; 1 byte gtrid length; 1 byte bqual length; and the characters of the XID name). Finally 5 compressed integers: the number of referenced oob nodes; the file number and offset of the first one; and the file number and offset of the last one. 113 : : +#. Type=6: This is an XA complete record, used for recovery purposes for internal 2-phase commit transactions and user XA. The first byte is a type byte, which is 0 for commit and 1 for XA rollback. Then follows 6-134 bytes of the XID, in the same format as for the XA PREPARE record. 114 : : + 115 : : +## Not supported 116 : : + 117 : : +A few things are not supported with the new binlog implementation. Some of these should be supported in a later version of MariaDB. 118 : : + 119 : : +#. Old-style filename/offset replication positions are not available with the new binlog. Slaves must be configured to use GTID (this is the default). Event offsets are generally reported as zero. `MASTER_POS_WAIT()` is not available, `MASTER_GTID_WAIT()` should be used instead. Similarly, `BINLOG_GTID_POS()` is not available. 120 : : +#. Semi-synchronous replication is not supported in the first version. It will be supported as normal eventually using the `AFTER_COMMIT` option. The `AFTER_SYNC` option cannot be supported, as the expensive two-phase commit between binlog and engine is no longer needed (`AFTER_SYNC` waits for slave acknowledgement in the middle of the two-phase commit). Likewise, `--init-rpl-role` is not supported. 121 : : +#. The new binlog implementation cannot be used with Galera. 122 : : +#. In the initial version, only InnoDB is available as an engine for the binlog (`--binlog-storage-engine=innodb`). It the future, other transactional storage engines could implement storing the binlog themselves (performance is best when the binlog is implemented in the same engine as the tables that are updated). 123 : : +#. The `sync_binlog` option is no longer needed and is effectively ignored. Since the binlog files are now crash-safe without needing any syncing. The durability of commits is now controlled solely by the `--innodb-flush-log-at-trx-commit` option, which now applies to both binlog files and InnoDB table data. 124 : : +#. The command `RESET MASTER TO` is not available with the new binlog. 125 : : +#. The `--tc-heuristic-recover` option is not needed with the new binlog and cannot be used. Any pending prepared transactions will always be rolled back or committed to be consistent with the binlog. If the binlog is empty (ie. has been deleted manually), pending transactions will be rolled back. 126 : : +#. Binlog encryption is not available. It is suggested to use filesystem-level encryption facilities of the operating system instead, and/or use SSL for the slave's connection to the master. 127 : : +#. SHOW BINLOG EVENTS FROM will not give an error for a position that starts in the middle of an event group. Instead, it will start from the first GTID event following the position (or return empty, if the position is past the end). ===== File: client/CMakeLists.txt ===== 77 : : MYSQL_ADD_EXECUTABLE(mariadb-plugin mysql_plugin.c) 78 : : TARGET_LINK_LIBRARIES(mariadb-plugin ${CLIENT_LIB}) 79 : : 80 : : +MYSQL_ADD_EXECUTABLE(mariadb-binlog mysqlbinlog.cc mysqlbinlog-engine.cc) 81 : : TARGET_LINK_LIBRARIES(mariadb-binlog ${CLIENT_LIB} mysys_ssl) 82 : : 83 : : MYSQL_ADD_EXECUTABLE(mariadb-admin mysqladmin.cc ../sql/password.c) ===== File: client/mysqlbinlog-engine.cc ===== 1 : : +/* Copyright (c) 2025, Kristian Nielsen. 2 : : + 3 : : + This program is free software; you can redistribute it and/or modify 4 : : + it under the terms of the GNU General Public License as published by 5 : : + the Free Software Foundation; version 2 of the License. 6 : : + 7 : : + This program is distributed in the hope that it will be useful, 8 : : + but WITHOUT ANY WARRANTY; without even the implied warranty of 9 : : + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 : : + GNU General Public License for more details. 11 : : + 12 : : + You should have received a copy of the GNU General Public License 13 : : + along with this program; if not, write to the Free Software 14 : : + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ 15 : : + 16 : : +/* 17 : : + Code for reading engine-implemented binlog from the mysqlbinlog client 18 : : + program. 19 : : +*/ 20 : : + 21 : : +#include 22 : : +#include 23 : : +#include 24 : : +#include "client_priv.h" 25 : : +#include "mysqlbinlog-engine.h" 26 : : +#include "my_compr_int.h" 27 : : +#include "my_dir.h" 28 : : + 29 : : + 30 : : +const char *INNODB_BINLOG_MAGIC= "\xfe\xfe\x0d\x01"; 31 : : +static constexpr uint32_t INNODB_BINLOG_FILE_VERS_MAJOR= 1; 32 : : + 33 : : +static uint32_t binlog_page_size; 34 : : + 35 : : +/* 36 : : + Some code here is copied from storage/innobase/handler/innodb_binlog.cc and 37 : : + storage/innodb_binlog/fsp/fsp_binlog.cc and modified for use in the 38 : : + mysqlbinlog command-line client. 39 : : + 40 : : + Normally it is desirable to share code rather than copy/modify it, but 41 : : + special considerations apply here: 42 : : + 43 : : + - Normally, it is desirable to share the code so that modifications to the 44 : : + logic are automatically kept in sync between the two use cases. However 45 : : + in the case of the binlog, non-backwards compatible changes are highly 46 : : + undesirable, and having a separate reader implementation in mysqlbinlog 47 : : + is actually useful to detect any unintended or non-desirable changes to 48 : : + the format that prevent old code from reading it. The binlog files 49 : : + should remain readable to old mysqlbinlog versions if at all possible, 50 : : + as well as to any other 3rd-party readers. 51 : : + 52 : : + - The main purpose of the code inside InnoDB is to very efficiently allow 53 : : + reading of binlog data concurrently with active writing threads and 54 : : + concurrently with page fifo asynchroneous flushing. In contrast, the 55 : : + purpose of the mysqlclient code is to have a simple stand-alone command 56 : : + line reader of the files. These two use cases are sufficiently 57 : : + different, and the code frameworks used for storage/innobase/ and 58 : : + client/ likewise sufficiently different, that code-sharing seems more 59 : : + troublesome than beneficial here. 60 : : +*/ 61 : : + 62 : : +static constexpr uint32_t BINLOG_PAGE_SIZE_MAX= 65536; 63 : : +#define BINLOG_PAGE_DATA 0 64 : : +#define BINLOG_PAGE_CHECKSUM 4 65 : : +#define BINLOG_PAGE_DATA_END BINLOG_PAGE_CHECKSUM 66 : : + 67 : : +#define BINLOG_NAME_BASE "binlog-" 68 : : +#define BINLOG_NAME_EXT ".ibb" 69 : : + 70 : : +enum fsp_binlog_chunk_types { 71 : : + /* Zero means no data, effectively EOF. */ 72 : : + FSP_BINLOG_TYPE_EMPTY= 0, 73 : : + /* A binlogged committed event group. */ 74 : : + FSP_BINLOG_TYPE_COMMIT= 1, 75 : : + /* A binlog GTID state record. */ 76 : : + FSP_BINLOG_TYPE_GTID_STATE= 2, 77 : : + /* Out-of-band event group data. */ 78 : : + FSP_BINLOG_TYPE_OOB_DATA= 3, 79 : : + /* Dummy record, use to fill remainder of page (eg. FLUSH BINARY LOGS). */ 80 : : + FSP_BINLOG_TYPE_DUMMY= 4, 81 : : + /* Must be one more than the last type. */ 82 : : + FSP_BINLOG_TYPE_END, 83 : : + 84 : : + /* Padding data at end of page. */ 85 : : + FSP_BINLOG_TYPE_FILLER= 0xff 86 : : +}; 87 : : +static constexpr uint32_t FSP_BINLOG_FLAG_BIT_CONT= 7; 88 : : +static constexpr uint32_t FSP_BINLOG_FLAG_CONT= (1 << FSP_BINLOG_FLAG_BIT_CONT); 89 : : +static constexpr uint32_t FSP_BINLOG_FLAG_BIT_LAST= 6; 90 : : +static constexpr uint32_t FSP_BINLOG_FLAG_LAST= (1 << FSP_BINLOG_FLAG_BIT_LAST); 91 : : +static constexpr uint32_t FSP_BINLOG_TYPE_MASK= 92 : : + ~(FSP_BINLOG_FLAG_CONT | FSP_BINLOG_FLAG_LAST); 93 : : +static constexpr uint64_t ALLOWED_NESTED_RECORDS= 94 : : + /* GTID STATE at start of page can occur in the middle of other record. */ 95 : : + ((uint64_t)1 << FSP_BINLOG_TYPE_GTID_STATE) | 96 : : + /* DUMMY data at tablespace end can occur in the middle of other record. */ 97 : : + ((uint64_t)1 << FSP_BINLOG_TYPE_DUMMY) 98 : : + ; 99 : : + 100 : : + 101 : : +static char binlog_dir[FN_REFLEN + 1]; 102 : : + 103 : : + 104 : : +class chunk_reader_mysqlbinlog { 105 : : +public: 106 : : + enum chunk_reader_status { 107 : : + CHUNK_READER_ERROR= -1, 108 : : + CHUNK_READER_EOF= 0, 109 : : + CHUNK_READER_FOUND= 1 110 : : + }; 111 : : + 112 : : + /* 113 : : + Current state, can be obtained from save_pos() and later passed to 114 : : + restore_pos(). 115 : : + */ 116 : : + struct saved_position { 117 : : + /* Current position file. */ 118 : : + uint64_t file_no; 119 : : + /* Current position page. */ 120 : : + uint32_t page_no; 121 : : + /* Start of current chunk inside page. */ 122 : : + uint32_t in_page_offset; 123 : : + /* 124 : : + The length of the current chunk, once the chunk type has been read. 125 : : + If 0, it means the chunk type (and length) has not yet been read. 126 : : + */ 127 : : + uint32_t chunk_len; 128 : : + /* The read position inside the current chunk. */ 129 : : + uint32_t chunk_read_offset; 130 : : + uchar chunk_type; 131 : : + /* When set, read will skip the current chunk, if any. */ 132 : : + bool skip_current; 133 : : + /* Set while we are in the middle of reading a record. */ 134 : : + bool in_record; 135 : : + } s; 136 : : + 137 : : + /* Length of the currently open file, valid if cur_file_handle != -1. */ 138 : : + uint64_t cur_file_length; 139 : : + /* Buffer for reading a page from a binlog file. */ 140 : : + uchar *page_buffer; 141 : : + /* Open file handle to tablespace file_no, or -1. */ 142 : : + File cur_file_handle; 143 : : + /* 144 : : + Flag used to skip the rest of any partial chunk we might be starting in 145 : : + the middle of. 146 : : + */ 147 : : + bool skipping_partial; 148 : : + /* If the s.file_no / s.page_no is loaded in the page buffer. */ 149 : : + bool page_loaded; 150 : : + 151 : : + chunk_reader_mysqlbinlog(); 152 : 312 : + void set_page_buf(uchar *in_page_buf) { page_buffer= in_page_buf; } 153 : : + ~chunk_reader_mysqlbinlog(); 154 : : + 155 : : + /* Current type, or FSP_BINLOG_TYPE_FILLER if between records. */ 156 : 4239 : + uchar cur_type() { return (uchar)(s.chunk_type & FSP_BINLOG_TYPE_MASK); } 157 : : + bool cur_is_cont() { return (s.chunk_type & FSP_BINLOG_FLAG_CONT) != 0; } 158 : 6988 : + bool end_of_record() { return !s.in_record; } 159 : 2950 : + bool is_end_of_page() noexcept 160 : : + { 161 : 2950 : + return s.in_page_offset >= binlog_page_size - (BINLOG_PAGE_DATA_END + 3); 162 : : + } 163 : 4347 : + bool is_end_of_file() noexcept 164 : : + { 165 : 4347 : + return current_pos() + (BINLOG_PAGE_DATA_END + 3) >= cur_file_length; 166 : : + } 167 : : + static int read_error_corruption(uint64_t file_no, uint64_t page_no, 168 : : + const char *msg); 169 : 0 : + int read_error_corruption(const char *msg) 170 : : + { 171 : 0 : + return read_error_corruption(s.file_no, s.page_no, msg); 172 : : + } 173 : : + enum chunk_reader_status fetch_current_page(); 174 : : + /* 175 : : + Try to read max_len bytes from a record into buffer. 176 : : + 177 : : + If multipage is true, will move across pages to read following 178 : : + continuation chunks, if any, to try and read max_len total bytes. Only if 179 : : + the record ends before max_len bytes is less amount of bytes returned. 180 : : + 181 : : + If multipage is false, will read as much is available on one page (up to 182 : : + max of max_len), and then return. 183 : : + 184 : : + Returns number of bytes read, or -1 for error. 185 : : + Returns 0 if the chunk_reader is pointing to start of a chunk at the end 186 : : + of the current binlog (ie. end-of-file). 187 : : + */ 188 : : + int read_data(uchar *buffer, int max_len, bool multipage); 189 : : + /* Read the file header of current file_no. */ 190 : : + int parse_file_header(); 191 : : + 192 : : + /* Save current position, and restore it later. */ 193 : 184 : + void save_pos(saved_position *out_pos) { *out_pos= s; } 194 : : + void restore_pos(saved_position *pos); 195 : : + void seek(uint64_t file_no, uint64_t offset); 196 : : + 197 : : + /* 198 : : + Make next read_data() skip any data from the current chunk (if any), and 199 : : + start reading data only from the beginning of the next chunk. */ 200 : 1017 : + void skip_current() { if (s.in_record) s.skip_current= true; } 201 : : + /* 202 : : + Used initially, after seeking potentially into the middle of a (commit) 203 : : + record, to skip any continuation chunks until we reach the start of the 204 : : + first real record. 205 : : + */ 206 : 3362 : + void skip_partial(bool skip) { skipping_partial= skip; } 207 : 4347 : + uint64_t current_pos() { 208 : 4347 : + return (s.page_no * binlog_page_size) + s.in_page_offset; 209 : : + } 210 : : + void set_fd(File fd); 211 : : +}; 212 : : + 213 : : + 214 : : +class oob_reader_mysqlbinlog { 215 : : + enum oob_states { 216 : : + /* The initial state, about to visit the node for the first time. */ 217 : : + ST_initial, 218 : : + /* State of leaf node while traversing the prior trees in the forest. */ 219 : : + ST_traversing_prior_trees, 220 : : + /* State of non-leaf node while traversing its left sub-tree. */ 221 : : + ST_traversing_left_child, 222 : : + /* State of non-leaf node while traversing its right sub-tree. */ 223 : : + ST_traversing_right_child, 224 : : + /* State of node while reading out its data. */ 225 : : + ST_self 226 : : + }; 227 : : + 228 : : + /* 229 : : + Stack entry for one node currently taking part in post-order traversal. 230 : : + We maintain a stack of pending nodes during the traversal, as the traversal 231 : : + happens in a state machine rather than by recursion. 232 : : + */ 233 : : + struct stack_entry { 234 : : + /* Saved position after reading header. */ 235 : : + chunk_reader_mysqlbinlog::saved_position saved_pos; 236 : : + /* The location of this node's OOB record. */ 237 : : + uint64_t file_no; 238 : : + uint64_t offset; 239 : : + /* Right child, to be traversed after left child. */ 240 : : + uint64_t right_file_no; 241 : : + uint64_t right_offset; 242 : : + /* Offset of real data in this node, after header. */ 243 : : + uint32_t header_len; 244 : : + /* Amount of data read into rd_buf, and amount used to parse header. */ 245 : : + uint32_t rd_buf_len; 246 : : + uint32_t rd_buf_sofar; 247 : : + /* Current state in post-order traversal state machine. */ 248 : : + enum oob_states state; 249 : : + /* Buffer for reading header. */ 250 : : + uchar rd_buf[5*COMPR_INT_MAX64]; 251 : : + /* 252 : : + True when the node is reached using only left child pointers, false 253 : : + otherwise. Used to identify the left-most leaf in a tree which points to 254 : : + a prior tree that must be traversed first. 255 : : + */ 256 : : + bool is_leftmost; 257 : : + }; 258 : : + std::vectorstack; 259 : : + 260 : : + /* State machine current state. */ 261 : : + enum oob_states state; 262 : : + 263 : : +public: 264 : : + oob_reader_mysqlbinlog(); 265 : : + ~oob_reader_mysqlbinlog(); 266 : : + 267 : : + void start_traversal(uint64_t file_no, uint64_t offset); 268 : 360 : + bool oob_traversal_done() { return stack.empty(); } 269 : : + int read_data(chunk_reader_mysqlbinlog *chunk_rd, uchar *buf, int max_len); 270 : : + 271 : : +private: 272 : : + void push_state(enum oob_states state, uint64_t file_no, uint64_t offset, 273 : : + bool is_leftmost); 274 : : +}; 275 : : + 276 : : + 277 : : +class binlog_reader_innodb : public handler_binlog_reader { 278 : : + enum reader_states { 279 : : + ST_read_next_event_group, ST_read_oob_data, ST_read_commit_record 280 : : + }; 281 : : + 282 : : + chunk_reader_mysqlbinlog chunk_rd; 283 : : + oob_reader_mysqlbinlog oob_reader; 284 : : + chunk_reader_mysqlbinlog::saved_position saved_commit_pos; 285 : : + 286 : : + /* Out-of-band data to read after commit record, if any. */ 287 : : + uint64_t oob_count; 288 : : + uint64_t oob_last_file_no; 289 : : + uint64_t oob_last_offset; 290 : : + /* Any secondary out-of-band data to be also read. */ 291 : : + uint64_t oob_count2; 292 : : + uint64_t oob_last_file_no2; 293 : : + uint64_t oob_last_offset2; 294 : : + /* 295 : : + The starting file_no. We stop once we've read the last record in this file 296 : : + (which may span into the next file). 297 : : + */ 298 : : + uint64_t start_file_no; 299 : : + /* Buffer to hold a page read directly from the binlog file. */ 300 : : + uchar *page_buf; 301 : : + /* Keep track of pending bytes in the rd_buf. */ 302 : : + uint32_t rd_buf_len; 303 : : + uint32_t rd_buf_sofar; 304 : : + /* State for state machine reading chunks one by one. */ 305 : : + enum reader_states state; 306 : : + 307 : : + /* Used to read the header of the commit record. */ 308 : : + uchar rd_buf[5*COMPR_INT_MAX64]; 309 : : +private: 310 : : + int read_data(uchar *buf, uint32_t len); 311 : : + 312 : : +public: 313 : : + binlog_reader_innodb(); 314 : : + virtual ~binlog_reader_innodb(); 315 : : + virtual int read_binlog_data(uchar *buf, uint32_t len) final; 316 : : + virtual bool data_available() final; 317 : : + virtual bool wait_available(THD *thd, const struct timespec *abstime) final; 318 : : + virtual int init_gtid_pos(THD *thd, slave_connection_state *pos, 319 : : + rpl_binlog_state_base *state) final; 320 : : + virtual int init_legacy_pos(THD *thd, const char *filename, 321 : : + ulonglong offset) final; 322 : : + virtual void enable_single_file() final; 323 : 316 : + bool is_valid() { return page_buf != nullptr; } 324 : : + bool init_from_fd_pos(File fd, ulonglong start_position); 325 : : +}; 326 : : + 327 : : + 328 : : +static int 329 : 1738 : +read_page_mysqlbinlog(File fd, uchar *buf, uint32_t page_no) noexcept 330 : : +{ 331 : 3476 : + size_t read= my_pread(fd, buf, binlog_page_size, 332 : 1738 : + (my_off_t)page_no * binlog_page_size, MYF(0)); 333 : 1738 : + int res= 1; 334 : 1738 : + if (likely(read == binlog_page_size)) 335 : : + { 336 : 1738 : + const uint32_t payload= (uint32_t)binlog_page_size - BINLOG_PAGE_CHECKSUM; 337 : 1738 : + uint32_t crc32= uint4korr(buf + payload); 338 : : + /* Allow a completely zero (empty) page as well. */ 339 : 2017 : + if (unlikely(crc32 != my_crc32c(0, buf, payload)) && 340 : 279 : + (buf[0] != 0 || 0 != memcmp(buf, buf+1, binlog_page_size - 1))) 341 : : + { 342 : 0 : + res= -1; 343 : 0 : + my_errno= EIO; 344 : : + } 345 : : + } 346 : 0 : + else if (read == (size_t)-1) 347 : 0 : + res= -1; 348 : : + else 349 : 0 : + res= 0; 350 : : + 351 : 1738 : + return res; 352 : : +} 353 : : + 354 : : + 355 : 312 : +chunk_reader_mysqlbinlog::chunk_reader_mysqlbinlog() 356 : 312 : + : s { 0, 0, 0, 0, 0, FSP_BINLOG_TYPE_FILLER, false, false }, 357 : 312 : + cur_file_length(0), 358 : 312 : + cur_file_handle((File)-1), 359 : 312 : + skipping_partial(false), page_loaded(false) 360 : : +{ 361 : 312 : +} 362 : : + 363 : : + 364 : 312 : +chunk_reader_mysqlbinlog::~chunk_reader_mysqlbinlog() 365 : : +{ 366 : 312 : + if (cur_file_handle >= (File)0) 367 : 312 : + my_close(cur_file_handle, MYF(0)); 368 : 312 : +} 369 : : + 370 : : + 371 : : +int 372 : 0 : +chunk_reader_mysqlbinlog::read_error_corruption(uint64_t file_no, uint64_t page_no, 373 : : + const char *msg) 374 : : +{ 375 : 0 : + error("Corrupt InnoDB binlog found on page %" PRIu64 " in binlog number " 376 : : + "%" PRIu64 ": %s", page_no, file_no, msg); 377 : 0 : + return -1; 378 : : +} 379 : : + 380 : : + 381 : : +int 382 : 8013 : +chunk_reader_mysqlbinlog::read_data(uchar *buffer, int max_len, bool multipage) 383 : : +{ 384 : : + uint32_t size; 385 : 8013 : + int sofar= 0; 386 : : + 387 : 8725 : +read_more_data: 388 : 8725 : + if (max_len == 0) 389 : 0 : + return sofar; 390 : : + 391 : 8725 : + if (!page_loaded) 392 : : + { 393 : 1422 : + enum chunk_reader_status res= fetch_current_page(); 394 : 1422 : + if (res == CHUNK_READER_EOF) 395 : 0 : + return 0; 396 : 1422 : + if (res == CHUNK_READER_ERROR) 397 : 0 : + return -1; 398 : : + } 399 : : + 400 : 8725 : + if (s.chunk_len == 0) 401 : : + { 402 : : + uchar type; 403 : : + /* 404 : : + This code gives warning "comparison of unsigned expression in ‘< 0’ is 405 : : + always false" when BINLOG_PAGE_DATA is 0. 406 : : + 407 : : + So use a static assert for now; if it ever triggers, replace it with this 408 : : + code: 409 : : + 410 : : + if (s.in_page_offset < BINLOG_PAGE_DATA) 411 : : + s.in_page_offset= BINLOG_PAGE_DATA; 412 : : + */ 413 : : + if (0) 414 : : + static_assert(BINLOG_PAGE_DATA == 0, 415 : : + "Replace static_assert with code from above comment"); 416 : : + 417 : : + /* Check for end-of-file. */ 418 : 4811 : + if ((s.page_no * binlog_page_size) + s.in_page_offset >= cur_file_length) 419 : 0 : + return sofar; 420 : : + 421 : 4811 : + if (s.in_page_offset >= binlog_page_size - (BINLOG_PAGE_DATA_END + 3) || 422 : 4811 : + page_buffer[s.in_page_offset] == FSP_BINLOG_TYPE_FILLER) 423 : : + { 424 : 0 : + DBUG_ASSERT(s.in_page_offset >= binlog_page_size - BINLOG_PAGE_DATA_END || 425 : : + page_buffer[s.in_page_offset] == FSP_BINLOG_TYPE_FILLER); 426 : 0 : + goto go_next_page; 427 : : + } 428 : : + 429 : 4811 : + type= page_buffer[s.in_page_offset]; 430 : 4811 : + if (type == 0) 431 : 280 : + return 0; 432 : : + 433 : : + /* 434 : : + Consistency check on the chunks. A record must consist in a sequence of 435 : : + chunks of the same type, all but the first must have the 436 : : + FSP_BINLOG_FLAG_BIT_CONT bit set, and the final one must have the 437 : : + FSP_BINLOG_FLAG_BIT_LAST bit set. 438 : : + */ 439 : 4531 : + if (!s.in_record) 440 : : + { 441 : 4037 : + if (type & FSP_BINLOG_FLAG_CONT && !s.skip_current) 442 : : + { 443 : 2 : + if (skipping_partial) 444 : : + { 445 : 2 : + s.chunk_len= page_buffer[s.in_page_offset + 1] | 446 : 2 : + ((uint32_t)page_buffer[s.in_page_offset + 2] << 8); 447 : 2 : + s.skip_current= true; 448 : 2 : + goto skip_chunk; 449 : : + } 450 : : + else 451 : 0 : + return read_error_corruption(s.file_no, s.page_no, "Binlog record " 452 : 0 : + "starts with continuation chunk"); 453 : : + } 454 : : + } 455 : : + else 456 : : + { 457 : 494 : + if ((type ^ s.chunk_type) & FSP_BINLOG_TYPE_MASK) 458 : : + { 459 : : + /* 460 : : + As a special case, we must allow a GTID state to appear in the 461 : : + middle of a record. 462 : : + */ 463 : 18 : + if (((uint64_t)1 << (type & FSP_BINLOG_TYPE_MASK)) & 464 : : + ALLOWED_NESTED_RECORDS) 465 : : + { 466 : 18 : + s.chunk_len= page_buffer[s.in_page_offset + 1] | 467 : 18 : + ((uint32_t)page_buffer[s.in_page_offset + 2] << 8); 468 : 18 : + goto skip_chunk; 469 : : + } 470 : : + /* Chunk type changed in the middle. */ 471 : 0 : + return read_error_corruption(s.file_no, s.page_no, "Binlog record missing " 472 : 0 : + "end chunk"); 473 : : + } 474 : 476 : + if (!(type & FSP_BINLOG_FLAG_CONT)) 475 : : + { 476 : : + /* START chunk without END chunk. */ 477 : 0 : + return read_error_corruption(s.file_no, s.page_no, "Binlog record missing " 478 : 0 : + "end chunk"); 479 : : + } 480 : : + } 481 : : + 482 : 4511 : + s.skip_current= false; 483 : 4511 : + s.chunk_type= type; 484 : 4511 : + s.in_record= true; 485 : 4511 : + s.chunk_len= page_buffer[s.in_page_offset + 1] | 486 : 4511 : + ((uint32_t)page_buffer[s.in_page_offset + 2] << 8); 487 : 4511 : + s.chunk_read_offset= 0; 488 : : + } 489 : : + 490 : : + /* Now we have a chunk available to read data from. */ 491 : 8425 : + DBUG_ASSERT(s.chunk_read_offset < s.chunk_len); 492 : 8425 : + if (s.skip_current && 493 : 692 : + (s.chunk_read_offset > 0 || (s.chunk_type & FSP_BINLOG_FLAG_CONT))) 494 : : + { 495 : : + /* 496 : : + Skip initial continuation chunks. 497 : : + Used to be able to start reading potentially in the middle of a record, 498 : : + ie. at a GTID state point. 499 : : + */ 500 : 692 : + s.chunk_read_offset= s.chunk_len; 501 : : + } 502 : : + else 503 : : + { 504 : 7733 : + size= std::min((uint32_t)max_len, s.chunk_len - s.chunk_read_offset); 505 : 7733 : + memcpy(buffer, page_buffer + s.in_page_offset + 3 + s.chunk_read_offset, size); 506 : 7733 : + buffer+= size; 507 : 7733 : + s.chunk_read_offset+= size; 508 : 7733 : + max_len-= size; 509 : 7733 : + sofar+= size; 510 : : + } 511 : : + 512 : 8425 : + if (s.chunk_len > s.chunk_read_offset) 513 : : + { 514 : 3914 : + DBUG_ASSERT(max_len == 0 /* otherwise would have read more */); 515 : 3914 : + return sofar; 516 : : + } 517 : : + 518 : : + /* We have read all of the chunk. Move to next chunk or end of the record. */ 519 : 4511 : +skip_chunk: 520 : 4531 : + s.in_page_offset+= 3 + s.chunk_len; 521 : 4531 : + s.chunk_len= 0; 522 : 4531 : + s.chunk_read_offset= 0; 523 : : + 524 : 4531 : + if (s.chunk_type & FSP_BINLOG_FLAG_LAST) 525 : : + { 526 : 4033 : + s.in_record= false; /* End of record. */ 527 : 4033 : + s.skip_current= false; 528 : : + } 529 : : + 530 : 4531 : + if (s.in_page_offset >= binlog_page_size - (BINLOG_PAGE_DATA_END + 3) && 531 : 772 : + (s.page_no * binlog_page_size) + s.in_page_offset < cur_file_length) 532 : : + { 533 : 772 : +go_next_page: 534 : : + /* End of page reached, move to the next page. */ 535 : 772 : + ++s.page_no; 536 : 772 : + page_loaded= false; 537 : 772 : + s.in_page_offset= 0; 538 : : + 539 : 772 : + if (cur_file_handle >= (File)0 && 540 : 772 : + (s.page_no * binlog_page_size) >= cur_file_length) 541 : : + { 542 : : + /* Move to the next file. */ 543 : 304 : + my_close(cur_file_handle, MYF(0)); 544 : 304 : + cur_file_handle= (File)-1; 545 : 304 : + cur_file_length= ~(uint64_t)0; 546 : 304 : + ++s.file_no; 547 : 304 : + s.page_no= 1; /* Skip the header page. */ 548 : : + } 549 : : + } 550 : : + 551 : 4531 : + if (sofar > 0 && (!multipage || !s.in_record)) 552 : 3819 : + return sofar; 553 : : + 554 : 712 : + goto read_more_data; 555 : : +} 556 : : + 557 : : + 558 : 312 : +oob_reader_mysqlbinlog::oob_reader_mysqlbinlog() 559 : : +{ 560 : : + /* Nothing. */ 561 : 312 : +} 562 : : + 563 : : + 564 : 312 : +oob_reader_mysqlbinlog::~oob_reader_mysqlbinlog() 565 : : +{ 566 : : + /* Nothing. */ 567 : 312 : +} 568 : : + 569 : : + 570 : : +void 571 : 190 : +oob_reader_mysqlbinlog::push_state(enum oob_states state, uint64_t file_no, 572 : : + uint64_t offset, bool is_leftmost) 573 : : +{ 574 : : + stack_entry new_entry; 575 : 190 : + new_entry.state= state; 576 : 190 : + new_entry.file_no= file_no; 577 : 190 : + new_entry.offset= offset; 578 : 190 : + new_entry.is_leftmost= is_leftmost; 579 : 190 : + stack.emplace_back(std::move(new_entry)); 580 : 190 : +} 581 : : + 582 : : + 583 : : +void 584 : 98 : +oob_reader_mysqlbinlog::start_traversal(uint64_t file_no, uint64_t offset) 585 : : +{ 586 : 98 : + stack.clear(); 587 : 98 : + push_state(ST_initial, file_no, offset, true); 588 : 98 : +} 589 : : + 590 : : + 591 : : +/* 592 : : + Read from out-of-band event group data. 593 : : + 594 : : + Does a state-machine incremental traversal of the forest of perfect binary 595 : : + trees of oob records in the event group. May read just the data available 596 : : + on one page, thus returning less than the requested number of bytes (this 597 : : + is to prefer to inspect each page only once, returning data page-by-page as 598 : : + long as reader asks for at least a full page worth of data). 599 : : +*/ 600 : : +int 601 : 360 : +oob_reader_mysqlbinlog::read_data(chunk_reader_mysqlbinlog *chunk_rd, 602 : : + uchar *buf, int len) 603 : : +{ 604 : : + stack_entry *e; 605 : : + uint64_t chunk_idx; 606 : : + uint64_t left_file_no; 607 : : + uint64_t left_offset; 608 : : + int res; 609 : : + const uchar *p_end; 610 : : + const uchar *p; 611 : 360 : + std::pair v_and_p; 612 : : + int size; 613 : : + 614 : 360 : + if (stack.empty()) 615 : : + { 616 : 0 : + DBUG_ASSERT(0 /* Should not call when no more oob data to read. */); 617 : 0 : + return 0; 618 : : + } 619 : : + 620 : 624 : +again: 621 : 624 : + e= &(stack[stack.size() - 1]); 622 : 624 : + switch (e->state) 623 : : + { 624 : 172 : + case ST_initial: 625 : 172 : + chunk_rd->seek(e->file_no, e->offset); 626 : : + static_assert(sizeof(e->rd_buf) == 5*COMPR_INT_MAX64, 627 : : + "rd_buf size must match code using it"); 628 : 172 : + res= chunk_rd->read_data(e->rd_buf, 5*COMPR_INT_MAX64, true); 629 : 172 : + if (res < 0) 630 : 0 : + return -1; 631 : 172 : + if (chunk_rd->cur_type() != FSP_BINLOG_TYPE_OOB_DATA) 632 : 0 : + return chunk_rd->read_error_corruption("Wrong chunk type"); 633 : 172 : + if (res == 0) 634 : 0 : + return chunk_rd->read_error_corruption("Unexpected EOF, expected " 635 : 0 : + "oob chunk"); 636 : 172 : + e->rd_buf_len= res; 637 : 172 : + p_end= e->rd_buf + res; 638 : 172 : + v_and_p= compr_int_read(e->rd_buf); 639 : 172 : + p= v_and_p.second; 640 : 172 : + if (p > p_end) 641 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 642 : 172 : + chunk_idx= v_and_p.first; 643 : : + (void)chunk_idx; 644 : : + 645 : 172 : + v_and_p= compr_int_read(p); 646 : 172 : + p= v_and_p.second; 647 : 172 : + if (p > p_end) 648 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 649 : 172 : + left_file_no= v_and_p.first; 650 : 172 : + v_and_p= compr_int_read(p); 651 : 172 : + p= v_and_p.second; 652 : 172 : + if (p > p_end) 653 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 654 : 172 : + left_offset= v_and_p.first; 655 : : + 656 : 172 : + v_and_p= compr_int_read(p); 657 : 172 : + p= v_and_p.second; 658 : 172 : + if (p > p_end) 659 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 660 : 172 : + e->right_file_no= v_and_p.first; 661 : 172 : + v_and_p= compr_int_read(p); 662 : 172 : + p= v_and_p.second; 663 : 172 : + if (p > p_end) 664 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 665 : 172 : + e->right_offset= v_and_p.first; 666 : 172 : + e->rd_buf_sofar= (uint32_t)(p - e->rd_buf); 667 : 172 : + if (left_file_no == 0 && left_offset == 0) 668 : : + { 669 : : + /* Leaf node. */ 670 : 168 : + if (e->is_leftmost && !(e->right_file_no == 0 && e->right_offset == 0)) 671 : : + { 672 : : + /* Traverse the prior tree(s) in the forst. */ 673 : 84 : + e->state= ST_traversing_prior_trees; 674 : 84 : + chunk_rd->save_pos(&e->saved_pos); 675 : 84 : + push_state(ST_initial, e->right_file_no, e->right_offset, true); 676 : : + } 677 : : + else 678 : 84 : + e->state= ST_self; 679 : : + } 680 : : + else 681 : : + { 682 : 4 : + e->state= ST_traversing_left_child; 683 : 4 : + chunk_rd->save_pos(&e->saved_pos); 684 : 4 : + push_state(ST_initial, left_file_no, left_offset, e->is_leftmost); 685 : : + } 686 : 172 : + goto again; 687 : : + 688 : 84 : + case ST_traversing_prior_trees: 689 : 84 : + chunk_rd->restore_pos(&e->saved_pos); 690 : 84 : + e->state= ST_self; 691 : 84 : + goto again; 692 : : + 693 : 4 : + case ST_traversing_left_child: 694 : 4 : + e->state= ST_traversing_right_child; 695 : 4 : + push_state(ST_initial, e->right_file_no, e->right_offset, false); 696 : 4 : + goto again; 697 : : + 698 : 4 : + case ST_traversing_right_child: 699 : 4 : + chunk_rd->restore_pos(&e->saved_pos); 700 : 4 : + e->state= ST_self; 701 : 4 : + goto again; 702 : : + 703 : 360 : + case ST_self: 704 : 360 : + size= 0; 705 : 360 : + if (e->rd_buf_len > e->rd_buf_sofar) 706 : : + { 707 : : + /* Use any excess data from when the header was read. */ 708 : 172 : + size= std::min((int)(e->rd_buf_len - e->rd_buf_sofar), len); 709 : 172 : + memcpy(buf, e->rd_buf + e->rd_buf_sofar, size); 710 : 172 : + e->rd_buf_sofar+= size; 711 : 172 : + len-= size; 712 : 172 : + buf+= size; 713 : : + } 714 : : + 715 : 360 : + if (len > 0 && !chunk_rd->end_of_record()) 716 : : + { 717 : 360 : + res= chunk_rd->read_data(buf, len, false); 718 : 360 : + if (res < 0) 719 : 0 : + return -1; 720 : 360 : + size+= res; 721 : : + } 722 : : + 723 : 360 : + if (chunk_rd->end_of_record()) 724 : : + { 725 : : + /* This oob record done, pop the state. */ 726 : 172 : + DBUG_ASSERT(!stack.empty()); 727 : 172 : + stack.erase(stack.end() - 1, stack.end()); 728 : : + } 729 : 360 : + return size; 730 : : + 731 : 0 : + default: 732 : 0 : + DBUG_ASSERT(0); 733 : 0 : + return -1; 734 : : + } 735 : : +} 736 : : + 737 : : + 738 : 312 : +binlog_reader_innodb::binlog_reader_innodb() 739 : 312 : + : oob_count(0), oob_last_file_no(0), oob_last_offset(0), 740 : 312 : + oob_count2(0), oob_last_file_no2(0), oob_last_offset2(0), 741 : 312 : + start_file_no(~(uint64_t)0), 742 : 312 : + rd_buf_len(0), rd_buf_sofar(0), state(ST_read_next_event_group) 743 : : +{ 744 : 312 : + page_buf= (uchar *) 745 : 312 : + my_malloc(PSI_NOT_INSTRUMENTED, BINLOG_PAGE_SIZE_MAX, MYF(MY_WME)); 746 : 312 : + chunk_rd.set_page_buf(page_buf); 747 : 312 : +} 748 : : + 749 : : + 750 : 624 : +binlog_reader_innodb::~binlog_reader_innodb() 751 : : +{ 752 : 312 : + my_free(page_buf); 753 : 624 : +} 754 : : + 755 : : + 756 : : +void 757 : 316 : +chunk_reader_mysqlbinlog::set_fd(File fd) 758 : : +{ 759 : 316 : + if (cur_file_handle != (File)-1) 760 : : + { 761 : 4 : + my_close(cur_file_handle, MYF(0)); 762 : 4 : + cur_file_length= ~(uint64_t)0; 763 : 4 : + page_loaded= false; 764 : : + } 765 : 316 : + cur_file_handle= fd; 766 : 316 : + my_off_t old_pos= my_tell(fd, MYF(0)); 767 : 316 : + if (old_pos != (my_off_t)-1) 768 : : + { 769 : : + /* Will be ~0 if we cannot seek the file. */ 770 : 316 : + cur_file_length= my_seek(fd, 0, SEEK_END, MYF(0)); 771 : 316 : + my_seek(fd, old_pos, SEEK_SET, MYF(0)); 772 : : + } 773 : 316 : +} 774 : : + 775 : : + 776 : : +bool 777 : 0 : +binlog_reader_innodb::data_available() 778 : : +{ 779 : 0 : + DBUG_ASSERT(0 /* Should not be used in mysqlbinlog. */); 780 : 0 : + return true; 781 : : +} 782 : : + 783 : : + 784 : : +bool 785 : 0 : +binlog_reader_innodb::wait_available(THD *thd, const struct timespec *abstime) 786 : : +{ 787 : 0 : + DBUG_ASSERT(0 /* Should not be used in mysqlbinlog. */); 788 : 0 : + return true; 789 : : +} 790 : : + 791 : : + 792 : : +int 793 : 0 : +binlog_reader_innodb::init_gtid_pos(THD *thd, slave_connection_state *pos, 794 : : + rpl_binlog_state_base *state) 795 : : +{ 796 : 0 : + DBUG_ASSERT(0 /* Should not be used in mysqlbinlog. */); 797 : 0 : + return 1; 798 : : +} 799 : : + 800 : : + 801 : : +int 802 : 0 : +binlog_reader_innodb::init_legacy_pos(THD *thd, const char *filename, 803 : : + ulonglong offset) 804 : : +{ 805 : 0 : + DBUG_ASSERT(0 /* Should not be used in mysqlbinlog. */); 806 : 0 : + return 1; 807 : : +} 808 : : + 809 : : + 810 : : +void 811 : 0 : +binlog_reader_innodb::enable_single_file() 812 : : +{ 813 : 0 : + DBUG_ASSERT(0 /* Should not be used in mysqlbinlog. */); 814 : 0 : +} 815 : : + 816 : : + 817 : : +int 818 : 1016 : +binlog_reader_innodb::read_binlog_data(uchar *buf, uint32_t len) 819 : : +{ 820 : 1016 : + int res= read_data(buf, len); 821 : 1016 : + return res; 822 : : +} 823 : : + 824 : : + 825 : : +bool 826 : 316 : +binlog_reader_innodb::init_from_fd_pos(File fd, ulonglong start_position) 827 : : +{ 828 : 316 : + chunk_rd.set_fd(fd); 829 : 316 : + if (chunk_rd.parse_file_header()) 830 : 0 : + return true; 831 : 316 : + uint64_t prev_start_file_no= start_file_no; 832 : 316 : + start_file_no= chunk_rd.s.file_no; 833 : 316 : + if (prev_start_file_no != ~(uint64_t)0 && 834 : 4 : + prev_start_file_no + 1 == chunk_rd.s.file_no) 835 : : + { 836 : : + /* Continuing in the file following the previous one. */ 837 : : + } 838 : : + else 839 : : + { 840 : 312 : + if (start_position < binlog_page_size) 841 : 312 : + start_position= binlog_page_size; 842 : 312 : + chunk_rd.seek(chunk_rd.s.file_no, (uint64_t)start_position); 843 : 312 : + chunk_rd.skip_partial(true); 844 : : + } 845 : 316 : + return false; 846 : : +} 847 : : + 848 : : + 849 : 1016 : +int binlog_reader_innodb::read_data(uchar *buf, uint32_t len) 850 : : +{ 851 : : + int res; 852 : : + const uchar *p_end; 853 : : + const uchar *p; 854 : 1016 : + std::pair v_and_p; 855 : 1016 : + int sofar= 0; 856 : : + 857 : 7017 : +again: 858 : 8033 : + switch (state) 859 : : + { 860 : 4539 : + case ST_read_next_event_group: 861 : 8886 : + if (chunk_rd.s.file_no > start_file_no || 862 : 4347 : + (chunk_rd.s.file_no == start_file_no && chunk_rd.is_end_of_file())) 863 : : + { 864 : : + /* 865 : : + We have read the entire file, return EOF. 866 : : + If the user specified to read the following file also, we may 867 : : + continue where we left in that file later. 868 : : + */ 869 : 192 : + return sofar; 870 : : + } 871 : : + static_assert(sizeof(rd_buf) == 5*COMPR_INT_MAX64, 872 : : + "rd_buf size must match code using it"); 873 : 4347 : + res= chunk_rd.read_data(rd_buf, 5*COMPR_INT_MAX64, true); 874 : 4347 : + if (res < 0) 875 : 0 : + return res; 876 : 4347 : + if (res == 0) 877 : 280 : + return sofar; 878 : 4067 : + if (chunk_rd.cur_type() != FSP_BINLOG_TYPE_COMMIT) 879 : : + { 880 : 1017 : + chunk_rd.skip_current(); 881 : 1017 : + goto again; 882 : : + } 883 : : + /* Found the start of a commit record. */ 884 : 3050 : + chunk_rd.skip_partial(false); 885 : : + 886 : : + /* Read the header of the commit record to see if there's any oob data. */ 887 : 3050 : + rd_buf_len= res; 888 : 3050 : + p_end= rd_buf + res; 889 : 3050 : + v_and_p= compr_int_read(rd_buf); 890 : 3050 : + p= v_and_p.second; 891 : 3050 : + if (p > p_end) 892 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 893 : 3050 : + oob_count= v_and_p.first; 894 : 3050 : + oob_count2= 0; 895 : : + 896 : 3050 : + if (oob_count > 0) 897 : : + { 898 : : + /* Skip the pointer to first chunk. */ 899 : 96 : + v_and_p= compr_int_read(p); 900 : 96 : + p= v_and_p.second; 901 : 96 : + if (p > p_end) 902 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 903 : 96 : + v_and_p= compr_int_read(p); 904 : 96 : + p= v_and_p.second; 905 : 96 : + if (p > p_end) 906 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 907 : : + 908 : 96 : + v_and_p= compr_int_read(p); 909 : 96 : + p= v_and_p.second; 910 : 96 : + if (p > p_end) 911 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 912 : 96 : + oob_last_file_no= v_and_p.first; 913 : 96 : + v_and_p= compr_int_read(p); 914 : 96 : + p= v_and_p.second; 915 : 96 : + if (p > p_end) 916 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 917 : 96 : + oob_last_offset= v_and_p.first; 918 : : + 919 : : + /* Check for any secondary oob data. */ 920 : 96 : + v_and_p= compr_int_read(p); 921 : 96 : + p= v_and_p.second; 922 : 96 : + if (p > p_end) 923 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 924 : 96 : + oob_count2= v_and_p.first; 925 : : + 926 : 96 : + if (oob_count2 > 0) 927 : : + { 928 : : + /* Skip the pointer to first chunk. */ 929 : 2 : + v_and_p= compr_int_read(p); 930 : 2 : + p= v_and_p.second; 931 : 2 : + if (p > p_end) 932 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 933 : 2 : + v_and_p= compr_int_read(p); 934 : 2 : + p= v_and_p.second; 935 : 2 : + if (p > p_end) 936 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 937 : : + 938 : 2 : + v_and_p= compr_int_read(p); 939 : 2 : + p= v_and_p.second; 940 : 2 : + if (p > p_end) 941 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 942 : 2 : + oob_last_file_no2= v_and_p.first; 943 : 2 : + v_and_p= compr_int_read(p); 944 : 2 : + p= v_and_p.second; 945 : 2 : + if (p > p_end) 946 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 947 : 2 : + oob_last_offset2= v_and_p.first; 948 : : + } 949 : : + } 950 : : + 951 : 3050 : + rd_buf_sofar= (uint32_t)(p - rd_buf); 952 : 3050 : + state= ST_read_commit_record; 953 : 3050 : + goto again; 954 : : + 955 : 3134 : + case ST_read_commit_record: 956 : 3134 : + if (rd_buf_len > rd_buf_sofar) 957 : : + { 958 : : + /* Use any excess data from when the header was read. */ 959 : 3050 : + int size= std::min((int)(rd_buf_len - rd_buf_sofar), (int)len); 960 : 3050 : + memcpy(buf, rd_buf + rd_buf_sofar, size); 961 : 3050 : + rd_buf_sofar+= size; 962 : 3050 : + len-= size; 963 : 3050 : + buf+= size; 964 : 3050 : + sofar+= size; 965 : : + } 966 : : + 967 : 3134 : + if (len > 0 && !chunk_rd.end_of_record()) 968 : : + { 969 : 3134 : + res= chunk_rd.read_data(buf, len, false); 970 : 3134 : + if (res < 0) 971 : 0 : + return -1; 972 : 3134 : + len-= res; 973 : 3134 : + buf+= res; 974 : 3134 : + sofar+= res; 975 : : + } 976 : : + 977 : 3134 : + if (rd_buf_sofar == rd_buf_len && chunk_rd.end_of_record()) 978 : : + { 979 : 3046 : + if (oob_count == 0) 980 : : + { 981 : 2950 : + state= ST_read_next_event_group; 982 : 2950 : + if (len > 0 && !chunk_rd.is_end_of_page()) 983 : : + { 984 : : + /* 985 : : + Let us try to read more data from this page. The goal is to read 986 : : + from each page only once, as long as caller passes in a buffer at 987 : : + least as big as our page size. Though commit record header that 988 : : + spans a page boundary or oob records can break this property. 989 : : + */ 990 : 2950 : + goto again; 991 : : + } 992 : : + } 993 : : + else 994 : : + { 995 : 96 : + oob_reader.start_traversal(oob_last_file_no, oob_last_offset); 996 : 96 : + chunk_rd.save_pos(&saved_commit_pos); 997 : 96 : + state= ST_read_oob_data; 998 : : + } 999 : 96 : + if (sofar == 0) 1000 : 0 : + goto again; 1001 : : + } 1002 : : + 1003 : 184 : + return sofar; 1004 : : + 1005 : 360 : + case ST_read_oob_data: 1006 : 360 : + res= oob_reader.read_data(&chunk_rd, buf, len); 1007 : 360 : + if (res < 0) 1008 : 0 : + return -1; 1009 : 360 : + if (oob_reader.oob_traversal_done()) 1010 : : + { 1011 : 80 : + if (oob_count2 > 0) 1012 : : + { 1013 : : + /* Switch over to secondary oob data. */ 1014 : 2 : + oob_count= oob_count2; 1015 : 2 : + oob_count2= 0; 1016 : 2 : + oob_last_file_no= oob_last_file_no2; 1017 : 2 : + oob_last_offset= oob_last_offset2; 1018 : 2 : + oob_reader.start_traversal(oob_last_file_no, oob_last_offset); 1019 : 2 : + state= ST_read_oob_data; 1020 : : + } 1021 : : + else 1022 : : + { 1023 : 78 : + chunk_rd.restore_pos(&saved_commit_pos); 1024 : 78 : + state= ST_read_next_event_group; 1025 : : + } 1026 : : + } 1027 : 360 : + if (res == 0) 1028 : : + { 1029 : 0 : + DBUG_ASSERT(0 /* Should have had oob_traversal_done() last time then. */); 1030 : 0 : + if (sofar == 0) 1031 : 0 : + goto again; 1032 : : + } 1033 : 360 : + return sofar + res; 1034 : : + 1035 : 0 : + default: 1036 : 0 : + DBUG_ASSERT(0); 1037 : 0 : + return -1; 1038 : : + } 1039 : : +} 1040 : : + 1041 : : + 1042 : : +int 1043 : 316 : +chunk_reader_mysqlbinlog::parse_file_header() 1044 : : +{ 1045 : 316 : + binlog_page_size= BINLOG_HEADER_PAGE_SIZE; // Until we get the real page size 1046 : 316 : + if (read_page_mysqlbinlog(cur_file_handle, page_buffer, 0) <= 0) 1047 : : + { 1048 : 0 : + error("Cannot read first page of InnoDB binlog file"); 1049 : 0 : + return -1; 1050 : : + } 1051 : 316 : + const uint32_t payload= BINLOG_HEADER_PAGE_SIZE - BINLOG_PAGE_CHECKSUM; 1052 : 316 : + uint32_t crc32= uint4korr(page_buffer + payload); 1053 : 316 : + if (crc32 != my_crc32c(0, page_buffer, payload)) 1054 : : + { 1055 : 0 : + error("Invalid checksum on first page, cannot read binlog file"); 1056 : 0 : + return -1; 1057 : : + } 1058 : 316 : + uint32_t vers_major= uint4korr(page_buffer + 8); 1059 : 316 : + if (vers_major > INNODB_BINLOG_FILE_VERS_MAJOR) 1060 : : + { 1061 : 0 : + error("Unsupported version of InnoDB binlog file, cannot read"); 1062 : 0 : + return -1; 1063 : : + } 1064 : 316 : + binlog_page_size= 1 << uint4korr(page_buffer + 4); 1065 : 316 : + s.file_no= uint8korr(page_buffer + 16); 1066 : 316 : + return 0; 1067 : : +} 1068 : : + 1069 : : + 1070 : : +enum chunk_reader_mysqlbinlog::chunk_reader_status 1071 : 1422 : +chunk_reader_mysqlbinlog::fetch_current_page() 1072 : : +{ 1073 : : + uint64_t offset; 1074 : 1422 : + page_loaded= false; 1075 : : + for (;;) 1076 : : + { 1077 : 1422 : + if (cur_file_handle < (File)0) 1078 : : + { 1079 : : + char filename[FN_REFLEN + 1]; 1080 : : + MY_STAT stat_buf; 1081 : : + 1082 : 322 : + snprintf(filename, FN_REFLEN, 1083 : : + "%s/" BINLOG_NAME_BASE "%06" PRIu64 BINLOG_NAME_EXT, 1084 : : + binlog_dir, s.file_no); 1085 : 322 : + cur_file_handle= my_open(filename, O_RDONLY | O_BINARY, MYF(MY_WME)); 1086 : 322 : + if (cur_file_handle < (File)0) { 1087 : 0 : + cur_file_handle= (File)-1; 1088 : 0 : + cur_file_length= ~(uint64_t)0; 1089 : : + /* 1090 : : + For mysqlbinlog where the user specifies the file, treat a missing 1091 : : + file as EOF, on the idea that we read as much as possible from what 1092 : : + the user supplied. But still use MY_WME in the my_open() to give 1093 : : + some indication that we stopped due to a missing file. 1094 : : + */ 1095 : 0 : + return errno == ENOENT ? CHUNK_READER_EOF : CHUNK_READER_ERROR; 1096 : : + } 1097 : 322 : + if (my_fstat(cur_file_handle, &stat_buf, MYF(0))) { 1098 : 0 : + error("Cannot stat() file '%s', errno: %d", filename, errno); 1099 : 0 : + my_close(cur_file_handle, MYF(0)); 1100 : 0 : + cur_file_handle= (File)-1; 1101 : 0 : + cur_file_length= ~(uint64_t)0; 1102 : 0 : + return CHUNK_READER_ERROR; 1103 : : + } 1104 : 322 : + cur_file_length= stat_buf.st_size; 1105 : : + } 1106 : : + 1107 : 1422 : + offset= (s.page_no * binlog_page_size) | s.in_page_offset; 1108 : 1422 : + if (offset >= cur_file_length) { 1109 : : + /* End of this file, move to the next one. */ 1110 : 0 : + goto_next_file: 1111 : 0 : + if (cur_file_handle >= (File)0) 1112 : : + { 1113 : 0 : + my_close(cur_file_handle, MYF(0)); 1114 : 0 : + cur_file_handle= (File)-1; 1115 : 0 : + cur_file_length= ~(uint64_t)0; 1116 : : + } 1117 : 0 : + ++s.file_no; 1118 : 0 : + s.page_no= 1; /* Skip the header page. */ 1119 : 0 : + continue; 1120 : : + } 1121 : 1422 : + break; 1122 : 0 : + } 1123 : : + 1124 : 1422 : + int res= read_page_mysqlbinlog(cur_file_handle, page_buffer, s.page_no); 1125 : 1422 : + if (res < 0) 1126 : 0 : + return CHUNK_READER_ERROR; 1127 : 1422 : + if (res == 0) 1128 : 0 : + goto goto_next_file; 1129 : 1422 : + page_loaded= true; 1130 : 1422 : + return CHUNK_READER_FOUND; 1131 : : +} 1132 : : + 1133 : : + 1134 : : +void 1135 : 650 : +chunk_reader_mysqlbinlog::restore_pos(chunk_reader_mysqlbinlog::saved_position *pos) 1136 : : +{ 1137 : 650 : + if (cur_file_handle != (File)-1 && pos->file_no != s.file_no) 1138 : : + { 1139 : : + /* Seek to a different file than currently open, close it. */ 1140 : 18 : + my_close(cur_file_handle, MYF(0)); 1141 : 18 : + cur_file_handle= (File)-1; 1142 : 18 : + cur_file_length= ~(uint64_t)0; 1143 : : + } 1144 : 650 : + s= *pos; 1145 : 650 : + page_loaded= false; 1146 : 650 : +} 1147 : : + 1148 : : + 1149 : : +void 1150 : 484 : +chunk_reader_mysqlbinlog::seek(uint64_t file_no, uint64_t offset) 1151 : : +{ 1152 : 484 : + saved_position pos { 1153 : 484 : + file_no, (uint32_t)(offset / binlog_page_size), 1154 : 484 : + (uint32_t)(offset % binlog_page_size), 1155 : 484 : + 0, 0, FSP_BINLOG_TYPE_FILLER, false, false }; 1156 : 484 : + restore_pos(&pos); 1157 : 484 : +} 1158 : : + 1159 : : + 1160 : : +bool 1161 : 316 : +open_engine_binlog(handler_binlog_reader *generic_reader, 1162 : : + ulonglong start_position, 1163 : : + const char *filename, IO_CACHE *opened_cache) 1164 : : +{ 1165 : 316 : + binlog_reader_innodb *reader= (binlog_reader_innodb *)generic_reader; 1166 : 316 : + if (!reader->is_valid()) 1167 : : + { 1168 : 0 : + error("Out of memory allocating page buffer"); 1169 : 0 : + return true; 1170 : : + } 1171 : : + static_assert(sizeof(binlog_dir) >= FN_REFLEN + 1, 1172 : : + "dirname_part() needs up to FN_REFLEN char buffer"); 1173 : : + size_t dummy; 1174 : 316 : + dirname_part(binlog_dir, filename, &dummy); 1175 : 316 : + if (!strlen(binlog_dir)) 1176 : 2 : + strncpy(binlog_dir, ".", sizeof(binlog_dir) - 1); 1177 : 316 : + return reader->init_from_fd_pos(dup(opened_cache->file), start_position); 1178 : : +} 1179 : : + 1180 : : + 1181 : : +handler_binlog_reader * 1182 : 312 : +get_binlog_reader_innodb() 1183 : : +{ 1184 : 312 : + return new binlog_reader_innodb(); 1185 : : +} ===== File: client/mysqlbinlog-engine.h ===== 1 : : +/* Copyright (c) 2025, Kristian Nielsen. 2 : : + 3 : : + This program is free software; you can redistribute it and/or modify 4 : : + it under the terms of the GNU General Public License as published by 5 : : + the Free Software Foundation; version 2 of the License. 6 : : + 7 : : + This program is distributed in the hope that it will be useful, 8 : : + but WITHOUT ANY WARRANTY; without even the implied warranty of 9 : : + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 : : + GNU General Public License for more details. 11 : : + 12 : : + You should have received a copy of the GNU General Public License 13 : : + along with this program; if not, write to the Free Software 14 : : + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ 15 : : + 16 : : +#include 17 : : +#include 18 : : + 19 : : +#include "handler_binlog_reader.h" 20 : : + 21 : : + 22 : : +static constexpr uint32_t BINLOG_HEADER_PAGE_SIZE= 512; 23 : : +extern const char *INNODB_BINLOG_MAGIC; 24 : : + 25 : : +extern handler_binlog_reader *get_binlog_reader_innodb(); 26 : : +extern bool open_engine_binlog(handler_binlog_reader *reader, 27 : : + ulonglong start_position, 28 : : + const char *filename, IO_CACHE *opened_cache); 29 : : + 30 : : + 31 : : +/* Shared functions defined in mysqlbinlog.cc */ 32 : : +extern void error(const char *format, ...) ATTRIBUTE_FORMAT(printf, 1, 2); ===== File: client/mysqlbinlog.cc ===== 43 : : #include "sql_priv.h" 44 : : #include "sql_basic_types.h" 45 : : #include 46 : : +#include "handler_binlog_reader.h" 47 : : +#include "mysqlbinlog-engine.h" 48 : : #include "log_event.h" 49 : : #include "compat56.h" 50 : : #include "sql_common.h" 110 : : { "mysqlbinlog", "mariadb-binlog", "client", "client-server", "client-mariadb", 111 : : 0 }; 112 : : 113 : : +void error(const char *format, ...) ATTRIBUTE_FORMAT(printf, 1, 2); 114 : : static void warning(const char *format, ...) ATTRIBUTE_FORMAT(printf, 1, 2); 115 : : 116 : : static bool one_database=0, one_table=0, to_last_remote_log= 0, disable_log_bin= 0; 204 : : OK_EOF, 205 : : }; 206 : : 207 : : + 208 : : +static enum Binlog_format { 209 : : + ORIGINAL_BINLOG_FORMAT, INNODB_BINLOG_FORMAT 210 : : +} binlog_format= ORIGINAL_BINLOG_FORMAT; 211 : : + 212 : : +static handler_binlog_reader *engine_binlog_reader; 213 : : + 214 : : + 215 : : /** 216 : : Pointer to the last read Annotate_rows_log_event. Having read an 217 : : Annotate_rows event, we should not print it immediately because all 699 : : { 700 : : /* 701 : : These events must be printed in base64 format, if printed. 702 : : + In the original binlog format (no --binlog-storage-engine), 703 : : base64 format requires a FD event to be safe, so if no FD 704 : : event has been printed, we give an error. Except if user 705 : : passed --short-form, because --short-form disables printing 706 : : row events. 707 : : */ 708 : : 709 : 44028 : + if (binlog_format == ORIGINAL_BINLOG_FORMAT && 710 : 43548 : + !print_event_info->printed_fd_event && !short_form && 711 : 12 : opt_base64_output_mode != BASE64_OUTPUT_DECODE_ROWS && 712 : 4 : opt_base64_output_mode != BASE64_OUTPUT_NEVER) 713 : : { 908 : : /* 909 : : Run time estimation of the output window configuration. 910 : : 911 : : + Do not validate GLLE information if start position is provided as a file 912 : : offset. 913 : : */ 914 : 146572 : if (ev_type == GTID_LIST_EVENT && ev->when) 1766 : : @param format Printf-style format string, followed by printf 1767 : : varargs. 1768 : : */ 1769 : 40 : +void error(const char *format,...) 1770 : : { 1771 : : va_list args; 1772 : 40 : va_start(args, format); 1862 : 46 : delete_dynamic(&binlog_events); 1863 : 46 : delete_dynamic(&events_in_stmt); 1864 : : } 1865 : 3343 : + delete engine_binlog_reader; 1866 : 6686 : DBUG_VOID_RETURN; 1867 : 3343 : } 1868 : : 2545 : 818 : if (position_gtid_filter && 2546 : 274 : position_gtid_filter->get_num_start_gtids() > 0) 2547 : : { 2548 : 64 : + to_last_remote_log= TRUE; 2549 : : char str_buf[256]; 2550 : 64 : String query_str(str_buf, sizeof(str_buf), system_charset_info); 2551 : 64 : query_str.length(0); 3001 : 0 : error("Failed reading header; probably an empty file."); 3002 : 0 : return ERROR_STOP; 3003 : : } 3004 : 2919 : + if (0 == memcmp(header, INNODB_BINLOG_MAGIC, sizeof(header))) 3005 : : + { 3006 : 316 : + binlog_format= INNODB_BINLOG_FORMAT; 3007 : 316 : + if (!engine_binlog_reader) 3008 : : + { 3009 : 312 : + engine_binlog_reader= get_binlog_reader_innodb(); 3010 : 312 : + if (!engine_binlog_reader) 3011 : : + { 3012 : 0 : + error("Out of memory setting up reader for InnoDB-implemented binlog."); 3013 : 0 : + return ERROR_STOP; 3014 : : + } 3015 : : + } 3016 : : + /* 3017 : : + New engine-implemented binlog always does checksum verification on the 3018 : : + page level. 3019 : : + */ 3020 : 316 : + opt_verify_binlog_checksum= 0; 3021 : : + /* 3022 : : + New engine-implemented binlog does not contain format description 3023 : : + events. 3024 : : + */ 3025 : 316 : + goto end; 3026 : : + } 3027 : 2603 : + else if (header[0] == '\0' && !memcmp(header, header+1, sizeof(header)-1)) 3028 : : + { 3029 : : + /* This is an empty InnoDB binlog file, pre-allocated but not yet used. */ 3030 : 4 : + return OK_EOF; 3031 : : + } 3032 : 2599 : + else if (memcmp(header, BINLOG_MAGIC, sizeof(header))) 3033 : : { 3034 : 0 : error("File is not a binary log file."); 3035 : 0 : return ERROR_STOP; 3139 : 114 : break; 3140 : : } 3141 : 122 : } 3142 : 2911 : +end: 3143 : 2911 : my_b_seek(file, pos); 3144 : 2911 : return OK_CONTINUE; 3145 : : } 3234 : 0 : error("Failed reading from file."); 3235 : 0 : goto err; 3236 : : } 3237 : 2911 : + if (binlog_format == INNODB_BINLOG_FORMAT) 3238 : : + { 3239 : 316 : + if (open_engine_binlog(engine_binlog_reader, start_position, logname, file)) 3240 : 0 : + goto err; 3241 : : + } 3242 : : for (;;) 3243 : : { 3244 : : char llbuff[21]; 3245 : 81851 : my_off_t old_off = my_b_tell(file); 3246 : : int read_error; 3247 : : + Log_event* ev; 3248 : : 3249 : 81851 : + if (binlog_format == INNODB_BINLOG_FORMAT) 3250 : : + { 3251 : 7546 : + String packet; 3252 : 7546 : + int res= engine_binlog_reader->read_log_event(&packet, 0, MAX_MAX_ALLOWED_PACKET); 3253 : 7546 : + if (res == LOG_READ_EOF) 3254 : : + { 3255 : 184 : + ev= nullptr; 3256 : 184 : + read_error= 0; 3257 : : + } 3258 : 7362 : + else if (res < 0) 3259 : : + { 3260 : 0 : + ev= nullptr; 3261 : 0 : + read_error= -1; 3262 : : + } 3263 : : + else 3264 : : + { 3265 : 7362 : + const char *errmsg= nullptr; 3266 : 7362 : + ev= Log_event::read_log_event((uchar *)packet.ptr(), packet.length(), 3267 : : + &errmsg, glob_description_event, 3268 : : + FALSE, FALSE); 3269 : 7362 : + if (!ev) 3270 : : + { 3271 : 0 : + error("Error reading event: %s", errmsg); 3272 : 0 : + read_error= -1; 3273 : : + } 3274 : : + else 3275 : : + { 3276 : 7362 : + ev->register_temp_buf((uchar *)packet.release(), true); 3277 : 7362 : + read_error= 0; 3278 : : + } 3279 : : + } 3280 : 7546 : + } 3281 : : + else 3282 : : + { 3283 : 74305 : + ev= Log_event::read_log_event(file, &read_error, 3284 : : + glob_description_event, 3285 : : + opt_verify_binlog_checksum); 3286 : : + } 3287 : 81851 : if (!ev) 3288 : : { 3289 : : /* 3309 : : the size of the event, unless the event is encrypted. 3310 : : */ 3311 : 79436 : DBUG_ASSERT( 3312 : : + binlog_format == INNODB_BINLOG_FORMAT || 3313 : : ((ev->get_type_code() == UNKNOWN_EVENT && 3314 : : ((Unknown_log_event *) ev)->what == Unknown_log_event::ENCRYPTED)) || 3315 : : old_off + ev->data_written == my_b_tell(file)); ===== File: extra/mariabackup/backup_copy.cc ===== 61 : : #include "backup_debug.h" 62 : : #include "backup_mysql.h" 63 : : #include 64 : : +#include 65 : : #ifdef _WIN32 66 : : #include /* rmdir */ 67 : : #endif 1675 : 302 : datadir_iter_t *it = NULL; 1676 : : datadir_node_t node; 1677 : : const char *dst_dir; 1678 : 302 : + ds_ctxt *ds_binlogs = NULL; 1679 : : 1680 : 302 : memset(&node, 0, sizeof(node)); 1681 : : 1801 : : 1802 : 300 : ds_destroy(ds_tmp); 1803 : : 1804 : : + /* Prepare destination directory for any InnoDB binlog files. */ 1805 : 300 : + dst_dir = dst_dir_buf.make(opt_binlog_directory); 1806 : 300 : + ds_binlogs = ds_create(dst_dir, DS_TYPE_LOCAL); 1807 : : + 1808 : : /* copy the rest of tablespaces */ 1809 : 300 : ds_tmp = ds_create(mysql_data_home, DS_TYPE_LOCAL); 1810 : : 1866 : : 1867 : 66695 : filename = base_name(node.filepath); 1868 : : 1869 : : + /* Copy InnoDB binlog files into --binlog-directory. */ 1870 : : + uint64_t file_no; 1871 : 66695 : + if (is_binlog_name(filename, &file_no)) { 1872 : 44 : + if (!(ret = copy_or_move_file(ds_binlogs, filename, filename, 1873 : : + dst_dir, 1))) { 1874 : 0 : + goto cleanup; 1875 : : + } 1876 : 44 : + continue; 1877 : : + } 1878 : : + 1879 : : /* skip .qp files */ 1880 : 66651 : if (filename_matches(filename, ext_list)) { 1881 : 631 : continue; 1936 : : 1937 : 300 : ds_tmp = NULL; 1938 : : 1939 : 300 : + if (ds_binlogs != NULL) { 1940 : 300 : + ds_destroy(ds_binlogs); 1941 : 300 : + ds_binlogs = NULL; 1942 : : + } 1943 : : + 1944 : 300 : return(ret); 1945 : 302 : } 1946 : : ===== File: extra/mariabackup/backup_mysql.cc ===== 385 : 596 : char *aria_log_dir_path_var= NULL; 386 : 596 : char *page_zip_level_var= NULL; 387 : 596 : char *ignore_db_dirs= NULL; 388 : 596 : + char *binlog_directory_var= NULL; 389 : : char *endptr; 390 : 596 : ulong server_version= mysql_get_server_version(connection); 391 : : 412 : : {"innodb_compression_level", &page_zip_level_var}, 413 : : {"ignore_db_dirs", &ignore_db_dirs}, 414 : : {"aria_log_dir_path", &aria_log_dir_path_var}, 415 : : + {"binlog_directory", &binlog_directory_var}, 416 : 596 : {NULL, NULL}}; 417 : : 418 : 596 : read_mysql_variables(connection, "SHOW VARIABLES", mysql_vars, true); 549 : 596 : if (ignore_db_dirs) 550 : 596 : xb_load_list_string(ignore_db_dirs, ",", register_ignore_db_dirs_filter); 551 : : 552 : 596 : + if (binlog_directory_var && *binlog_directory_var) 553 : : + { 554 : 2 : + if (free_opt_binlog_directory) 555 : 0 : + my_free(const_cast(opt_binlog_directory)); 556 : 2 : + opt_binlog_directory= my_strdup(PSI_NOT_INSTRUMENTED, binlog_directory_var, 557 : : + MYF(MY_FAE)); 558 : 2 : + free_opt_binlog_directory= true; 559 : : + } 560 : : + 561 : 594 : out: 562 : : 563 : 596 : return (ret); ===== File: extra/mariabackup/common_engine.cc ===== 9 : : #include 10 : : #include 11 : : 12 : : +#include "innodb_binlog.h" 13 : : + 14 : : + 15 : : namespace common_engine { 16 : : 17 : : class Table { 301 : 576 : } 302 : : bool copy_log_tables(bool finalize); 303 : : bool copy_stats_tables(); 304 : : + bool copy_engine_binlogs(const char *binlog_dir, lsn_t backup_lsn); 305 : : bool wait_for_finish(); 306 : : bool close_log_tables(); 307 : : private: 308 : : 309 : : void process_table_job(Table *table, bool no_lock, bool delete_table, 310 : : bool finalize, unsigned thread_num); 311 : : + void process_binlog_job(std::string src, std::string dst, 312 : : + lsn_t backup_lsn, unsigned thread_num); 313 : : 314 : : const char *m_datadir_path; 315 : : ds_ctxt_t *m_ds; 316 : : std::vector &m_con_pool; 317 : : TasksGroup m_process_table_jobs; 318 : : + std::unique_ptr m_page_buf; 319 : : 320 : : post_copy_table_hook_t m_table_post_copy_hook; 321 : : std::unordered_map> m_log_tables; 344 : 3423 : m_process_table_jobs.finish_task(result); 345 : 3424 : } 346 : : 347 : 58 : +void BackupImpl::process_binlog_job(std::string src, std::string dst, 348 : : + lsn_t backup_lsn, unsigned thread_num) { 349 : 58 : + int result = 0; 350 : 58 : + const char *c_src= src.c_str(); 351 : 58 : + bool is_empty= true; 352 : : + lsn_t start_lsn; 353 : : + int binlog_found; 354 : : + 355 : 58 : + if (!m_process_table_jobs.get_result()) 356 : 0 : + goto exit; 357 : : + 358 : 58 : + binlog_found= get_binlog_header(c_src, m_page_buf.get(), start_lsn, is_empty); 359 : 58 : + if (binlog_found > 0 && !is_empty && start_lsn <= backup_lsn) { 360 : : + // Test binlog_in_engine.mariabackup_binlogs will try to inject 361 : : + // RESET MASTER and PURGE BINARY LOGS here. 362 : 44 : + DBUG_EXECUTE_IF("binlog_copy_sleep_2", 363 : : + if (src.find("binlog-000002.ibb") != 364 : : + std::string::npos) 365 : : + my_sleep(2000000);); 366 : 44 : + if (!m_ds->copy_file(c_src, dst.c_str(), thread_num)) 367 : 0 : + goto exit; 368 : : + } 369 : : + 370 : 58 : + result = 1; 371 : : + 372 : 58 : +exit: 373 : 58 : + m_process_table_jobs.finish_task(result); 374 : 58 : +} 375 : : + 376 : 1150 : bool BackupImpl::scan(const std::unordered_set &exclude_tables, 377 : : std::unordered_set *out_processed_tables, bool no_lock, 378 : : bool collect_log_and_stats) { 497 : 568 : return true; 498 : : } 499 : : 500 : 566 : +bool BackupImpl::copy_engine_binlogs(const char *binlog_dir, lsn_t backup_lsn) { 501 : 566 : + std::vectorfiles; 502 : 566 : + std::string dir(binlog_dir && binlog_dir[0] ? binlog_dir : m_datadir_path); 503 : 566 : + foreach_file_in_datadir(dir.c_str(), 504 : 566 : + [&](const char *name)->bool { 505 : : + uint64_t file_no; 506 : 8084 : + if (is_binlog_name(name, &file_no)) 507 : 58 : + files.emplace_back(name); 508 : 8084 : + return true; 509 : : + }); 510 : 566 : + m_page_buf.reset(new byte [ibb_page_size]); 511 : 624 : + for (auto &file : files) { 512 : 58 : + std::string path(dir + "/" + file); 513 : 58 : + m_process_table_jobs.push_task( 514 : 116 : + std::bind(&BackupImpl::process_binlog_job, this, path, 515 : : + file, backup_lsn, std::placeholders::_1)); 516 : 624 : + } 517 : 566 : + return true; 518 : 566 : +} 519 : : + 520 : 2846 : bool BackupImpl::wait_for_finish() { 521 : : /* Wait for threads to exit */ 522 : 2846 : return m_process_table_jobs.wait_for_finish(); 555 : 568 : return m_backup_impl->copy_stats_tables(); 556 : : } 557 : : 558 : 566 : +bool Backup::copy_engine_binlogs(const char *binlog_dir, lsn_t backup_lsn) { 559 : 566 : + return m_backup_impl->copy_engine_binlogs(binlog_dir, backup_lsn); 560 : : +} 561 : : + 562 : 2278 : bool Backup::wait_for_finish() { 563 : 2278 : return m_backup_impl->wait_for_finish(); 564 : : } ===== File: extra/mariabackup/common_engine.h ===== 28 : : bool no_lock, bool collect_log_and_stats); 29 : : bool copy_log_tables(bool finalize); 30 : : bool copy_stats_tables(); 31 : : + bool copy_engine_binlogs(const char *binlog_dir, lsn_t backup_lsn); 32 : : bool wait_for_finish(); 33 : : bool close_log_tables(); 34 : : void set_post_copy_table_hook(const post_copy_table_hook_t &hook); ===== File: extra/mariabackup/xtrabackup.cc ===== 376 : : 377 : : my_bool opt_lock_ddl_per_table = FALSE; 378 : : static my_bool opt_check_privileges; 379 : : +my_bool opt_backup_binlog= TRUE; 380 : : 381 : : extern const char *innodb_checksum_algorithm_names[]; 382 : : extern TYPELIB innodb_checksum_algorithm_typelib; 400 : : char *opt_user; 401 : : const char *opt_password; 402 : : bool free_opt_password; 403 : : +bool free_opt_binlog_directory= false; 404 : : char *opt_host; 405 : : char *opt_defaults_group; 406 : : char *opt_socket; 1455 : : OPT_XB_IGNORE_INNODB_PAGE_CORRUPTION, 1456 : : OPT_INNODB_FORCE_RECOVERY, 1457 : : OPT_INNODB_CHECKPOINT, 1458 : : + OPT_ARIA_LOG_DIR_PATH, 1459 : : + OPT_BINLOG, 1460 : : + OPT_BINLOG_DIRECTORY 1461 : : }; 1462 : : 1463 : : struct my_option xb_client_options[]= { 2122 : : (G_PTR *) &xtrabackup_help, (G_PTR *) &xtrabackup_help, 0, 2123 : : GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, 2124 : : 2125 : : + {"binlog", OPT_BINLOG, 2126 : : + "Backup the server binary logs. Only applies to server configured with " 2127 : : + "--binlog-storage-engine, old-style binlog is not backed up. Enabled by " 2128 : : + "default, specify --skip-binlog to not backup the binlog files. The " 2129 : : + "--skip-binlog option, if used, must be specified with both --backup and " 2130 : : + "--prepare", 2131 : : + (G_PTR*)&opt_backup_binlog, 2132 : : + (G_PTR*)&opt_backup_binlog, 2133 : : + 0, GET_BOOL, OPT_ARG, 1, 0, 0, 0, 0, 0}, 2134 : : + 2135 : : + {"binlog-directory", OPT_BINLOG_DIRECTORY, 2136 : : + "Directory containing binlog files, if different from datadir." 2137 : : + "Has effect only if server is using --binlog-storage-engine=innodb", 2138 : : + &opt_binlog_directory, &opt_binlog_directory, 2139 : : + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, 2140 : : + 2141 : : { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} 2142 : : }; 2143 : : 2453 : 0 : if (my_handle_options_init_variables) 2454 : 0 : fprintf(stderr, "Obsolete option: %s. Ignored\n", opt->name); 2455 : 0 : break; 2456 : 12 : + case OPT_BINLOG_DIRECTORY: 2457 : : + 2458 : 12 : + ADD_PRINT_PARAM_OPT(opt_binlog_directory); 2459 : 12 : + break; 2460 : : #define MYSQL_CLIENT 2461 : : #include "sslopt-case.h" 2462 : : #undef MYSQL_CLIENT 2624 : 530 : srv_undo_dir = (char*) "."; 2625 : : } 2626 : : 2627 : 1110 : + if (!opt_binlog_directory || !xtrabackup_backup) { 2628 : 1108 : + if (free_opt_binlog_directory) 2629 : 0 : + my_free(const_cast(opt_binlog_directory)); 2630 : 1108 : + opt_binlog_directory = "."; 2631 : 1108 : + free_opt_binlog_directory= false; 2632 : : + } 2633 : : + 2634 : : compile_time_assert(SRV_FORCE_IGNORE_CORRUPT == 1); 2635 : : 2636 : : /* 5422 : 568 : return res; 5423 : : } 5424 : : 5425 : 566 : + bool do_backup_binlogs() { 5426 : : + // Copy InnoDB binlog files. 5427 : : + // Going to BACKUP STAGE START protects against RESET 5428 : : + // MASTER deleting files during the copy, or FLUSH 5429 : : + // BINARY LOGS truncating them. 5430 : 566 : + if (!opt_no_lock) 5431 : 560 : + xb_mysql_query(mysql_connection, "BACKUP STAGE START", 5432 : : + false, false); 5433 : 566 : + if (!m_common_backup.copy_engine_binlogs(opt_binlog_directory, 5434 : : + recv_sys.lsn)) { 5435 : 0 : + msg("Error on copy InnoDB binlog files"); 5436 : 0 : + return false; 5437 : : + } 5438 : 566 : + if (!m_common_backup.wait_for_finish()) { 5439 : 0 : + msg("InnoDB binlog file backup process is finished with error"); 5440 : 0 : + return false; 5441 : : + } 5442 : 566 : + if (!opt_no_lock) 5443 : 560 : + xb_mysql_query(mysql_connection, "BACKUP STAGE END", 5444 : : + false, false); 5445 : 566 : + return true; 5446 : : + } 5447 : : + 5448 : 568 : bool stage_end(Backup_datasinks &backup_datasinks) { 5449 : 568 : msg("Starting BACKUP STAGE END"); 5450 : : /* release all locks */ 5465 : : nullptr); 5466 : : ); 5467 : : 5468 : 568 : + if (opt_backup_binlog) { 5469 : 566 : + if (!do_backup_binlogs()) 5470 : 0 : + return false; 5471 : : + } 5472 : : + 5473 : 568 : backup_finish(backup_datasinks.m_data); 5474 : 568 : return true; 5475 : : } 7751 : 14 : my_free((char*) opt_password); 7752 : 1340 : plugin_shutdown(); 7753 : 1340 : free_list(opt_plugin_load_list_ptr); 7754 : 1340 : + if (free_opt_binlog_directory) 7755 : 2 : + my_free(const_cast(opt_binlog_directory)); 7756 : 1340 : mysql_server_end(); 7757 : 1340 : sys_var_end(); 7758 : : ===== File: extra/mariabackup/xtrabackup.h ===== 151 : : 152 : : extern char *opt_user; 153 : : extern const char *opt_password; 154 : : +extern bool free_opt_binlog_directory; 155 : : extern char *opt_host; 156 : : extern char *opt_defaults_group; 157 : : extern char *opt_socket; 179 : : enum binlog_info_enum { BINLOG_INFO_OFF, BINLOG_INFO_LOCKLESS, BINLOG_INFO_ON, 180 : : BINLOG_INFO_AUTO}; 181 : : 182 : : +extern bool backup_binlog; 183 : : extern ulong opt_binlog_info; 184 : : 185 : : extern ulong xtrabackup_innodb_force_recovery; ===== File: include/handler_binlog_reader.h ===== 1 : : +#ifndef HANDLER_BINLOG_READER_INCLUDED 2 : : +#define HANDLER_BINLOG_READER_INCLUDED 3 : : + 4 : : +/* Copyright (c) 2025, Kristian Nielsen. 5 : : + 6 : : + This program is free software; you can redistribute it and/or modify 7 : : + it under the terms of the GNU General Public License as published by 8 : : + the Free Software Foundation; version 2 of the License. 9 : : + 10 : : + This program is distributed in the hope that it will be useful, 11 : : + but WITHOUT ANY WARRANTY; without even the implied warranty of 12 : : + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 : : + GNU General Public License for more details. 14 : : + 15 : : + You should have received a copy of the GNU General Public License 16 : : + along with this program; if not, write to the Free Software 17 : : + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ 18 : : + 19 : : + 20 : : +class String; 21 : : +class THD; 22 : : +struct slave_connection_state; 23 : : +struct rpl_binlog_state_base; 24 : : + 25 : : + 26 : : +/* 27 : : + Class for reading a binlog implemented in an engine. 28 : : +*/ 29 : : +class handler_binlog_reader { 30 : : +public: 31 : : + /* 32 : : + Approximate current position (from which next call to read_binlog_data() 33 : : + will need to read). Updated by the engine. Used to know which binlog files 34 : : + the active dump threads are currently reading from, to avoid purging 35 : : + actively used binlogs. 36 : : + */ 37 : : + uint64_t cur_file_no; 38 : : + uint64_t cur_file_pos; 39 : : + 40 : : +private: 41 : : + /* Position and length of any remaining data in buf[]. */ 42 : : + uint32_t buf_data_pos; 43 : : + uint32_t buf_data_remain; 44 : : + /* Buffer used when reading data out via read_binlog_data(). */ 45 : : + static constexpr size_t BUF_SIZE= 32768; 46 : : + uchar *buf; 47 : : + 48 : : +public: 49 : 2516 : + handler_binlog_reader() 50 : 2516 : + : cur_file_no(~(uint64_t)0), cur_file_pos(~(uint64_t)0), 51 : 2516 : + buf_data_pos(0), buf_data_remain(0) 52 : : + { 53 : 2516 : + buf= (uchar *)my_malloc(PSI_INSTRUMENT_ME, BUF_SIZE, MYF(0)); 54 : 2516 : + } 55 : 2514 : + virtual ~handler_binlog_reader() { 56 : 2514 : + my_free(buf); 57 : 2514 : + }; 58 : : + virtual int read_binlog_data(uchar *buf, uint32_t len) = 0; 59 : : + virtual bool data_available()= 0; 60 : : + /* 61 : : + Wait for data to be available to read, for kill, or for timeout. 62 : : + Returns true in case of timeout reached, false otherwise. 63 : : + Caller should check for kill before calling again (to avoid busy-loop). 64 : : + */ 65 : : + virtual bool wait_available(THD *thd, const struct timespec *abstime) = 0; 66 : : + /* 67 : : + This initializes the current read position to the point of the slave GTID 68 : : + position passed in as POS. It is permissible to start at a position a bit 69 : : + earlier in the binlog, only cost is the extra read cost of reading not 70 : : + needed event data. 71 : : + 72 : : + If position is found, must return the corresponding binlog state in the 73 : : + STATE output parameter and initialize cur_file_no and cur_file_pos members. 74 : : + 75 : : + Returns: 76 : : + -1 Error 77 : : + 0 The requested GTID position not found, needed binlogs have been purged 78 : : + 1 Ok, position found and returned. 79 : : + */ 80 : : + virtual int init_gtid_pos(THD *thd, slave_connection_state *pos, 81 : : + rpl_binlog_state_base *state) = 0; 82 : : + /* 83 : : + Initialize to a legacy-type position (filename, offset). This mostly to 84 : : + support legacy SHOW BINLOG EVENTS. 85 : : + */ 86 : : + virtual int init_legacy_pos(THD *thd, const char *filename, 87 : : + ulonglong offset) = 0; 88 : : + /* 89 : : + Can be called after init_gtid_pos() or init_legacy_pos() to make the reader 90 : : + stop (return EOF) at the end of the binlog file. Used for SHOW BINLOG 91 : : + EVENTS, which has a file-based interface based on legacy file name. 92 : : + */ 93 : : + virtual void enable_single_file() = 0; 94 : : + int read_log_event(String *packet, uint32_t ev_offset, size_t max_allowed); 95 : : +}; 96 : : + 97 : : +#endif /* HANDLER_BINLOG_READER_INCLUDED */ ===== File: include/my_bit.h ===== 221 : : } 222 : : C_MODE_END 223 : : 224 : : +/* 225 : : +The helper function my_nlz(x) calculates the number of leading zeros 226 : : +in the binary representation of the number "x", either using a 227 : : +built-in compiler function or a substitute trick based on the use 228 : : +of the multiplication operation and a table indexed by the prefix 229 : : +of the multiplication result: 230 : : + 231 : : +Moved to mysys from ha_innodb.cc to be able to use in non-InnoDB code. 232 : : +*/ 233 : : +#ifdef __GNUC__ 234 : : +#define my_nlz(x) __builtin_clzll(x) 235 : : +#elif defined(_MSC_VER) && !defined(_M_CEE_PURE) && \ 236 : : + (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64)) 237 : : +#ifndef __INTRIN_H_ 238 : : +#pragma warning(push, 4) 239 : : +#pragma warning(disable: 4255 4668) 240 : : +#include 241 : : +#pragma warning(pop) 242 : : +#endif 243 : : +__forceinline unsigned int my_nlz (unsigned long long x) 244 : : +{ 245 : : +#if defined(_M_IX86) || defined(_M_X64) 246 : : + unsigned long n; 247 : : +#ifdef _M_X64 248 : : + _BitScanReverse64(&n, x); 249 : : + return (unsigned int) n ^ 63; 250 : : +#else 251 : : + unsigned long y = (unsigned long) (x >> 32); 252 : : + unsigned int m = 31; 253 : : + if (y == 0) 254 : : + { 255 : : + y = (unsigned long) x; 256 : : + m = 63; 257 : : + } 258 : : + _BitScanReverse(&n, y); 259 : : + return (unsigned int) n ^ m; 260 : : +#endif 261 : : +#elif defined(_M_ARM64) 262 : : + return _CountLeadingZeros64(x); 263 : : +#endif 264 : : +} 265 : : +#else 266 : : +inline unsigned int my_nlz (unsigned long long x) 267 : : +{ 268 : : + static unsigned char table [48] = { 269 : : + 32, 6, 5, 0, 4, 12, 0, 20, 270 : : + 15, 3, 11, 0, 0, 18, 25, 31, 271 : : + 8, 14, 2, 0, 10, 0, 0, 0, 272 : : + 0, 0, 0, 21, 0, 0, 19, 26, 273 : : + 7, 0, 13, 0, 16, 1, 22, 27, 274 : : + 9, 0, 17, 23, 28, 24, 29, 30 275 : : + }; 276 : : + unsigned int y= (unsigned int) (x >> 32); 277 : : + unsigned int n= 0; 278 : : + if (y == 0) { 279 : : + y= (unsigned int) x; 280 : : + n= 32; 281 : : + } 282 : : + y = y | (y >> 1); // Propagate leftmost 1-bit to the right. 283 : : + y = y | (y >> 2); 284 : : + y = y | (y >> 4); 285 : : + y = y | (y >> 8); 286 : : + y = y & ~(y >> 16); 287 : : + y = y * 0x3EF5D037; 288 : : + return n + table[y >> 26]; 289 : : +} 290 : : +#endif 291 : : + 292 : : #endif /* MY_BIT_INCLUDED */ ===== File: include/my_compr_int.h ===== 1 : : +/***************************************************************************** 2 : : + 3 : : +Copyright (c) 2024 Kristian Nielsen. 4 : : + 5 : : +This program is free software; you can redistribute it and/or modify it under 6 : : +the terms of the GNU General Public License as published by the Free Software 7 : : +Foundation; version 2 of the License. 8 : : + 9 : : +This program is distributed in the hope that it will be useful, but WITHOUT 10 : : +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 : : +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 : : + 13 : : +You should have received a copy of the GNU General Public License along with 14 : : +this program; if not, write to the Free Software Foundation, Inc., 15 : : +51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA 16 : : + 17 : : +*****************************************************************************/ 18 : : + 19 : : +/* 20 : : + Reading and writing of compressed integers. 21 : : + 22 : : + Created 2024-10-01 Kristian Nielsen 23 : : +*/ 24 : : + 25 : : +#ifndef MY_COMPR_INT_H 26 : : +#define MY_COMPR_INT_H 27 : : + 28 : : +#include "my_bit.h" 29 : : +#include 30 : : +#include 31 : : + 32 : : + 33 : : +/* 34 : : + Read and write compressed (up to) 64-bit integers. 35 : : + 36 : : + A 64-bit number is encoded with 1-9 bytes. The 3 first bits stores a tag 37 : : + that determines the number of bytes used, and the encoding is written in 38 : : + little-endian format as (TAG | (NUMBER << 3)). The tag is the number of 39 : : + bytes used minus 1, except that 7 denotes 9 bytes used (numbers are never 40 : : + encoded with 8 bytes). For example: 41 : : + 42 : : + Number Encoding 43 : : + 0 0x00 44 : : + 0x1f 0xf8 (0 | (0x1f << 3)) 45 : : + 0x20 0x01 0x01 46 : : + 0xf6 0xb1 0x07 47 : : + 0xd34a 0x52 0x9a 0x06 48 : : + 0x1fffffffffffff 0xfe 0xff 0xff 0xff 0xff 0xff 0xff 49 : : + 0x20000000000000 0x07 0x00 0x00 0x00 0x00 0x00 0x00 0x01 0x00 50 : : + 0xffffffffffffffff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0x07 51 : : + 52 : : + The main advantage over something like base-128 compression (also called 53 : : + varint) is that the encoding and decoding can happen with just a single 54 : : + conditional jump to determine if one or two 64-bit words are involved (or 55 : : + even no or only well-predicted conditional jump if unaligned reads/writes 56 : : + and buffer padding can be assumed). 57 : : +*/ 58 : : + 59 : : +#define COMPR_INT_MAX32 5 60 : : +#define COMPR_INT_MAX64 9 61 : : +#define COMPR_INT_MAX COMPR_INT_MAX64 62 : : + 63 : : +/* Write compressed unsigned integer */ 64 : : +extern unsigned char *compr_int_write(unsigned char *p, uint64_t v); 65 : : +/* 66 : : + Read compressed integer. 67 : : + Returns a pair of the value read and the incremented pointer. 68 : : +*/ 69 : : +extern std::pair 70 : : + compr_int_read(const unsigned char *p); 71 : : + 72 : : +#endif /* MY_COMPR_INT_H */ ===== File: include/rpl_gtid_base.h ===== 1 : : +/* Copyright (c) 2013,2024, Kristian Nielsen and MariaDB Services Ab. 2 : : + 3 : : + This program is free software; you can redistribute it and/or modify 4 : : + it under the terms of the GNU General Public License as published by 5 : : + the Free Software Foundation; version 2 of the License. 6 : : + 7 : : + This program is distributed in the hope that it will be useful, 8 : : + but WITHOUT ANY WARRANTY; without even the implied warranty of 9 : : + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 : : + GNU General Public License for more details. 11 : : + 12 : : + You should have received a copy of the GNU General Public License 13 : : + along with this program; if not, write to the Free Software 14 : : + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ 15 : : + 16 : : +#ifndef RPL_GTID_BASE_H 17 : : +#define RPL_GTID_BASE_H 18 : : + 19 : : +#include "hash.h" 20 : : + 21 : : + 22 : : +/* Definitions for MariaDB global transaction ID (GTID). */ 23 : : + 24 : : +struct slave_connection_state; 25 : : + 26 : : +struct rpl_gtid 27 : : +{ 28 : : + uint32 domain_id; 29 : : + uint32 server_id; 30 : : + uint64 seq_no; 31 : : +}; 32 : : + 33 : : +/* 34 : : + Binlog state. 35 : : + 36 : : + A binlog state records the last GTID written to the binlog for every 37 : : + distinct (domain_id, server_id) pair. Thus, each point in the binlog 38 : : + corresponds to a specific binlog state. 39 : : + 40 : : + When starting replication from a specific GTID position, the starting point 41 : : + is identified as the most recent one where the binlog state has no higher 42 : : + seq_no than the GTID position for any (domain_id, server_id) combination. 43 : : + 44 : : + We also remember the most recent logged GTID for every domain_id. This is 45 : : + used to know where to start when a master is changed to a slave. As a side 46 : : + effect, it also allows to skip a hash lookup in the very common case of 47 : : + logging a new GTID with same server id as last GTID. 48 : : + 49 : : + This base class rpl_binlog_state_base contains just be basic data operations 50 : : + to insert/update GTIDs, and is used eg. from Gtid_index_*. 51 : : +*/ 52 : : +struct rpl_binlog_state_base 53 : : +{ 54 : : + struct element { 55 : : + uint32 domain_id; 56 : : + HASH hash; /* Containing all server_id for one domain_id */ 57 : : + /* The most recent entry in the hash. */ 58 : : + rpl_gtid *last_gtid; 59 : : + /* Counter to allocate next seq_no for this domain. */ 60 : : + uint64 seq_no_counter; 61 : : + 62 : : + int update_element(const rpl_gtid *gtid); 63 : : + }; 64 : : + 65 : : + /* Mapping from domain_id to collection of elements. */ 66 : : + HASH hash; 67 : : + my_bool initialized; 68 : : + 69 : 300835 : + rpl_binlog_state_base() : initialized(0) {} 70 : : + ~rpl_binlog_state_base(); 71 : : + void init(); 72 : : + void reset_nolock(); 73 : : + void free(); 74 : : + bool load_nolock(struct rpl_gtid *list, uint32 count); 75 : : + bool load_nolock(rpl_binlog_state_base *orig_state); 76 : : + int update_nolock(const struct rpl_gtid *gtid); 77 : : + int alloc_element_nolock(const rpl_gtid *gtid); 78 : : + uint32 count_nolock(); 79 : : + int get_gtid_list_nolock(rpl_gtid *gtid_list, uint32 list_size); 80 : : + rpl_gtid *find_nolock(uint32 domain_id, uint32 server_id); 81 : : + bool is_before_pos(slave_connection_state *pos); 82 : : + 83 : : + /* 84 : : + Inline iterator over a binlog state, most recent GTID comes last for each 85 : : + domain just like get_gtid_list_nolock(). 86 : : + 87 : : + The ITERATOR_FUNC should have signature bool f(const rpl_gtid *), and 88 : : + return true in case of error (in which case iterate() aborts and also 89 : : + returns true). 90 : : + 91 : : + Intended to do custom GTID state processing without requiring the overhead 92 : : + of an intermediate list, and where the extra code generation is justified. 93 : : + */ 94 : 189225 : + template bool iterate(F iterator_func) 95 : : + { 96 : : + uint32 i, j; 97 : 189225 : + ulong outer_records= hash.records; 98 : : + 99 : 2798790 : + for (i= 0; i < outer_records; ++i) 100 : : + { 101 : 2609628 : + element *e= (element *)my_hash_element(&hash, i); 102 : 2609628 : + ulong inner_records= e->hash.records; 103 : 2609628 : + const rpl_gtid *last_gtid= e->last_gtid; 104 : 2609628 : + if (unlikely(!last_gtid)) 105 : : + { 106 : 386 : + DBUG_ASSERT(inner_records==0); 107 : 386 : + continue; 108 : : + } 109 : 8231562 : + for (j= 0; j <= inner_records; ++j) 110 : : + { 111 : : + const rpl_gtid *gtid; 112 : 5622383 : + if (j < inner_records) 113 : : + { 114 : 3013142 : + gtid= (rpl_gtid *)my_hash_element(&e->hash, j); 115 : 3013141 : + if (gtid == last_gtid) 116 : 2609241 : + continue; 117 : : + } 118 : : + else 119 : 2609241 : + gtid= e->last_gtid; 120 : 3013141 : + if (iterator_func(gtid)) 121 : 62 : + return true; 122 : : + } 123 : : + } 124 : : + 125 : 189162 : + return false; // No error 126 : : + } 127 : : +}; 128 : : + 129 : : + 130 : : +#endif /* RPL_GTID_BASE_H */ ===== File: mysys/CMakeLists.txt ===== 47 : : my_rdtsc.c psi_noop.c 48 : : my_atomic_writes.c my_cpu.c my_likely.c my_largepage.c 49 : : file_logger.c my_dlerror.c crc32/crc32c.cc 50 : : + my_timezone.cc my_compr_int.cc my_thread_name.cc 51 : : my_virtual_mem.c) 52 : : 53 : : IF (WIN32) ===== File: mysys/mf_iocache.c ===== 1791 : 26877782 : if ((length=(size_t) (info->write_pos - info->write_buffer))) 1792 : : { 1793 : 25978458 : my_off_t eof= info->end_of_file + info->write_pos - info->append_read_pos; 1794 : : + /* 1795 : : + The write_function() updates info->pos_in_file. So compute the new end 1796 : : + position here before calling it, but update the value only after we 1797 : : + check for error return. 1798 : : + */ 1799 : 25978458 : + uchar *new_write_end= (info->write_buffer + info->buffer_length - 1800 : 25978458 : + ((info->pos_in_file + length) & (IO_SIZE - 1))); 1801 : 25978458 : if (append_cache) 1802 : : { 1803 : 4376623 : if (tmp_file_track(info, eof) || 1822 : : } 1823 : 23790850 : set_if_bigger(info->end_of_file, info->pos_in_file); 1824 : : } 1825 : 25979160 : + info->write_end= new_write_end; 1826 : 25979160 : info->write_pos= info->write_buffer; 1827 : 25979160 : ++info->disk_writes; 1828 : 25979160 : UNLOCK_APPEND_BUFFER; ===== File: mysys/my_compr_int.cc ===== 1 : : +/***************************************************************************** 2 : : + 3 : : +Copyright (c) 2024 Kristian Nielsen. 4 : : + 5 : : +This program is free software; you can redistribute it and/or modify it under 6 : : +the terms of the GNU General Public License as published by the Free Software 7 : : +Foundation; version 2 of the License. 8 : : + 9 : : +This program is distributed in the hope that it will be useful, but WITHOUT 10 : : +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 : : +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 : : + 13 : : +You should have received a copy of the GNU General Public License along with 14 : : +this program; if not, write to the Free Software Foundation, Inc., 15 : : +51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA 16 : : + 17 : : +*****************************************************************************/ 18 : : + 19 : : +/* 20 : : + Reading and writing of compressed integers. 21 : : + 22 : : + Created 2024-10-01 Kristian Nielsen 23 : : +*/ 24 : : + 25 : : +#include "mysys_priv.h" 26 : : +#include "my_compr_int.h" 27 : : + 28 : : +/* Read and write compressed (up to) 64-bit integers. */ 29 : : + 30 : : +/* 31 : : + Write compressed unsigned integer, efficient version without assuming 32 : : + unaligned writes. 33 : : +*/ 34 : 704576 : +unsigned char *compr_int_write(unsigned char *p, uint64_t v) { 35 : : + // Compute bytes needed to store the value v plus 3 bits encoding length. 36 : 704576 : + uint32_t needed_bits_minus_1= 66 - my_nlz(v|1); 37 : 704576 : + uint32_t needed_bytes= (needed_bits_minus_1 >> 3) + 1; 38 : : + 39 : : + // Compute the encoding of the length. 40 : : + // We need 1-9 bytes. Use 9 bytes instead of 8, so we can encode the 41 : : + // length in 3 bits for (1, 2, ..., 7, or 9 bytes). 42 : 704576 : + uint32_t bytes= needed_bytes | (needed_bytes >> 3); 43 : 704576 : + uint32_t len= needed_bytes - 1; 44 : : + // Encode 1-7 as 0-6, and encode 8,9 both as 8. 45 : 704576 : + len-= (len >> 3); 46 : : + 47 : : + // Compute the first 64-bit word to write. 48 : 704576 : + uintptr_t offset= (uintptr_t)p & (uintptr_t)7; 49 : 704576 : + uintptr_t offset_bits= offset << 3; 50 : 704576 : + uint64_t v1= (len | (v << 3)) << offset_bits; 51 : 704576 : + uint64_t *p1= (uint64_t *)(p - offset); 52 : 704576 : + uint64_t mask1= ~(uint64_t)0 << offset_bits; 53 : : + 54 : : + // Compute the second word to write (if any). 55 : 704576 : + uint64_t v2= v >> ((64 - 3) - offset_bits); 56 : 704576 : + uint64_t *p2= p1 + 1; 57 : : + 58 : : + // Write the value into next one or two 64-bit words, as needed. 59 : : + // Two words are needed if (offset + bytes) cross into the next word. 60 : : +#ifdef WORDS_BIGENDIAN 61 : : + /* 62 : : + Here it might be possible to use a slightly more efficient endian 63 : : + conversion on big-endian, since we know the pointer is 8-byte aligned. 64 : : + */ 65 : : + int8store((unsigned char *)p1, 66 : : + (uint8korr((unsigned char *)p1) & ~mask1) | v1); 67 : : +#else 68 : 704576 : + *p1= (*p1 & ~mask1) | v1; 69 : : +#endif 70 : 704576 : + if (offset + bytes >= 8) { 71 : : +#ifdef WORDS_BIGENDIAN 72 : : + int8store((unsigned char *)p2, v2); 73 : : +#else 74 : 232420 : + *p2= v2; 75 : : +#endif 76 : : + } 77 : 704576 : + return p + bytes; 78 : : +} 79 : : + 80 : : + 81 : : +/* 82 : : + Read compressed integer, efficient version without assuming unaligned reads. 83 : : + Returns a pair of the value read and the incremented pointer. 84 : : +*/ 85 : : +std::pair 86 : 878202 : +compr_int_read(const unsigned char *p) 87 : : +{ 88 : 878202 : + uintptr_t offset= (uintptr_t)p & (uintptr_t)7; 89 : 878202 : + uintptr_t offset_bits= offset << 3; 90 : 878202 : + const uint64_t *p_align= (const uint64_t *)((uintptr_t)p & ~(uintptr_t)7); 91 : : +#ifdef WORDS_BIGENDIAN 92 : : + /* 93 : : + Here it might be possible to use a slightly more efficient endian 94 : : + conversion on big-endian, since we know the pointer is 8-byte aligned. 95 : : + */ 96 : : + uint64_t v1= uint8korr((unsigned char *)p_align); 97 : : +#else 98 : 878202 : + uint64_t v1= p_align[0]; 99 : : +#endif 100 : 878202 : + uint32_t len= (v1 >> offset_bits) & 7; 101 : 878202 : + uint32_t bytes= len + 1; 102 : 878202 : + bytes+= (bytes >> 3); 103 : : + uint64_t v; 104 : 878202 : + if (offset + bytes > 8) { 105 : 185337 : + uint64_t mask2= (~(uint64_t)0) >> ((16 - (offset + bytes)) << 3); 106 : : +#ifdef WORDS_BIGENDIAN 107 : : + v= (v1 >> (3 + offset_bits)) | 108 : : + ((uint8korr((unsigned char *)(p_align + 1)) & mask2) << 109 : : + (61 - offset_bits)); 110 : : +#else 111 : 185337 : + v= (v1 >> (3 + offset_bits)) | ((p_align[1] & mask2) << (61 - offset_bits)); 112 : : +#endif 113 : : + } else { 114 : 692865 : + uint64_t mask1= (~(uint64_t)0) >> ((7 - (offset + len)) << 3); 115 : 692865 : + v= (v1 & mask1) >> (3 + offset_bits); 116 : : + } 117 : 878202 : + return std::pair(v, p + bytes); 118 : : +} 119 : : + 120 : : + 121 : : +#ifdef TEST_MAIN 122 : : +#include 123 : : +#include 124 : : +#include 125 : : + 126 : : + 127 : : +// Smaller version that assumes unaligned writes of 8-bit values is ok, and 128 : : +// that there are up to 8 scratch bytes available after the value written. 129 : : +unsigned char *compr_int_write_le_unaligned_buffer(unsigned char *p, uint64_t v) { 130 : : + // Compute bytes needed to store the value v plus 3 bits encoding length. 131 : : + uint32_t needed_bits_minus_1= 66 - my_nlz(v|1); 132 : : + uint32_t needed_bytes= (needed_bits_minus_1 >> 3) + 1; 133 : : + 134 : : + // Compute the encoding of the length. 135 : : + // We need 1-9 bytes. Use 9 bytes instead of 8, so we can encode the 136 : : + // length in 3 bits for (1, 2, ..., 7, or 9 bytes). 137 : : + uint32_t bytes= needed_bytes | (needed_bytes >> 3); 138 : : + uint32_t len= needed_bytes - 1; 139 : : + // Encode 1-7 as 0-6, and encode 8,9 both as 8. 140 : : + len-= (len >> 3); 141 : : + 142 : : + // Write the (up to) 9 bytes, prefering redundant write to conditional jump. 143 : : + *(uint64_t *)p= len | (v << 3); 144 : : + *(p+8)= v >> 63; 145 : : + return p + bytes; 146 : : +} 147 : : + 148 : : +// Generic version without assumptions. 149 : : +unsigned char *compr_int_write_generic(unsigned char *p, uint64_t v) { 150 : : + // Compute bytes needed to store the value v plus 3 bits encoding length. 151 : : + uint32_t needed_bits_minus_1= 66 - my_nlz(v|1); 152 : : + uint32_t needed_bytes= (needed_bits_minus_1 >> 3) + 1; 153 : : + 154 : : + // Compute the encoding of the length. 155 : : + // We need 1-9 bytes. Use 9 bytes instead of 8, so we can encode the 156 : : + // length in 3 bits for (1, 2, ..., 7, or 9 bytes). 157 : : + uint32_t bytes= needed_bytes | (needed_bytes >> 3); 158 : : + uint32_t len= needed_bytes - 1; 159 : : + // Encode 1-7 as 0-6, and encode 8,9 both as 8. 160 : : + len-= (len >> 3); 161 : : + 162 : : + // Write the necessary bytes out. 163 : : + *p++= len | (v << 3); 164 : : + v >>= 5; 165 : : + while (--bytes > 0) { 166 : : + *p++= v; 167 : : + v>>= 8; 168 : : + } 169 : : + return p; 170 : : + 171 : : +} 172 : : + 173 : : + 174 : : +// Generic read compressed integers. 175 : : +std::pair 176 : : +compr_int_read_generic(const unsigned char *p) 177 : : +{ 178 : : + uint64_t v= *p++; 179 : : + uint32_t bytes= v & 7; 180 : : + v>>= 3; 181 : : + uint32_t shift= 5; 182 : : + bytes+= ((bytes + 1) >> 3); // A 7 means read 8 bytes more (9 total) 183 : : + while (bytes-- > 0) { 184 : : + v|= ((uint64_t)(*p++)) << shift; 185 : : + shift+= 8; 186 : : + } 187 : : + return std::pair(v, p); 188 : : +} 189 : : + 190 : : + 191 : : +// Read compressed integers assuming little-endian, efficient unaligned 192 : : +// 64-bit reads, and up to 7 bytes of scratch space after. 193 : : +std::pair 194 : : +compr_int_read_le_unaligned_buf(const unsigned char *p) 195 : : +{ 196 : : + uint64_t v= *(const uint64_t *)p; 197 : : + uint32_t len= v & 7; 198 : : + uint64_t mask= (~(uint64_t)0) >> ((7 - len) << 3); 199 : : + v= (v & mask) >> 3; 200 : : + // Need for extra read is assumed rare, well-predicted conditional jump 201 : : + // likely faster. 202 : : + uint32_t bytes= len + 1; 203 : : + if (__builtin_expect((len == 7), 0)) { 204 : : + v|= ((uint64_t)p[8]) << 61; // Add last 3 bits 205 : : + bytes+= 1; // 7 means read 9 bytes 206 : : + } 207 : : + return std::pair(v, p + bytes); 208 : : +} 209 : : + 210 : : + 211 : : +int 212 : : +main(int argc, char *argv[]) 213 : : +{ 214 : : + int N= (argc > 1 ? atoi(argv[1]) : 1000); 215 : : + uint64_t *src= new uint64_t[N]; 216 : : + unsigned char *buf1= new unsigned char[N*9]; 217 : : + unsigned char *buf2= new unsigned char[N*9]; 218 : : + int i; 219 : : + 220 : : + // Generate test data. 221 : : + for (i= 0; i < N; ++i) 222 : : + src[i]= ((uint64_t)1 << (rand() % 64)) + (uint64_t)(rand()); 223 : : + // Write test data. 224 : : + unsigned char *p1= buf1; 225 : : + unsigned char *p2= buf2; 226 : : + for (i= 0; i < N; ++i) { 227 : : + p1= compr_int_write(p1, src[i]); 228 : : + p2= compr_int_write_generic(p2, src[i]); 229 : : + } 230 : : + if ((p1 - buf1) != (p2 - buf2)) { 231 : : + fprintf(stderr, "Write error! Mismatch lengths of optimised and generic.\n"); 232 : : + } else { 233 : : + if (memcmp(buf1, buf2, p1 - buf1)) 234 : : + fprintf(stderr, "Write error! Mismatch data of optimised and generic.\n"); 235 : : + } 236 : : + // Verify written data. 237 : : + std::pairq1, q2; 238 : : + const unsigned char *c1= buf1; 239 : : + const unsigned char *c2= buf2; 240 : : + for (i= 0; i < N; ++i) { 241 : : + q1= compr_int_read(c1); 242 : : + q2= compr_int_read_generic(c2); 243 : : + uint64_t v1= q1.first; 244 : : + c1= q1.second; 245 : : + uint64_t v2= q2.first; 246 : : + c2= q2.second; 247 : : + if (v1 != v2 || v1 != src[i]) { 248 : : + fprintf(stderr, "Read error! mismatch values @ i=%d 0x%llx 0x%llx 0x%llx.\n", 249 : : + i, (unsigned long long)v1, (unsigned long long)v2, 250 : : + (unsigned long long)(src[i])); 251 : : + break; 252 : : + } 253 : : + } 254 : : + return 0; 255 : : +} 256 : : + 257 : : +#endif /* TEST_MAIN */ ===== File: sql/handler.cc ===== 1536 : : 1537 : 32751 : if (ha_info) 1538 : : { 1539 : 32583 : + if (unlikely(tc_log->log_xa_prepare(thd, all))) 1540 : : + { 1541 : 450 : + ha_rollback_trans(thd, all); 1542 : 450 : + error= 1; 1543 : 450 : + goto binlog_error; 1544 : : + } 1545 : 92366 : for (; ha_info; ha_info= ha_info->next()) 1546 : : { 1547 : 60235 : transaction_participant *ht= ha_info->ht(); 1565 : : } 1566 : : } 1567 : : 1568 : 32131 : +binlog_error: 1569 : 32583 : DEBUG_SYNC(thd, "at_unlog_xa_prepare"); 1570 : : 1571 : 32583 : if (tc_log->unlog_xa_prepare(thd, all)) 2007 : : */ 2008 : 2437532 : if (! hi->is_trx_read_write()) 2009 : 697 : continue; 2010 : : + /* We do not need to 2pc the binlog with the engine that implements it. */ 2011 : 2436835 : + if (ht == opt_binlog_engine_hton) 2012 : 301800 : + continue; 2013 : : /* 2014 : : Sic: we know that prepare() is not NULL since otherwise 2015 : : trans->no_2pc would have been set. 2069 : 0 : if (wsrep_must_abort(thd)) 2070 : : { 2071 : 0 : mysql_mutex_unlock(&thd->LOCK_thd_data); 2072 : 0 : + (void)tc_log->unlog(thd, cookie, xid); 2073 : 0 : goto wsrep_err; 2074 : : } 2075 : 0 : mysql_mutex_unlock(&thd->LOCK_thd_data); 2076 : : } 2077 : : #endif /* WITH_WSREP */ 2078 : 392440 : DBUG_EXECUTE_IF("crash_commit_before_unlog", DBUG_SUICIDE();); 2079 : 392452 : + if (tc_log->unlog(thd, cookie, xid)) 2080 : 60 : error= 2; /* Error during commit */ 2081 : : 2082 : 392369 : done: 2235 : : { 2236 : 5762690 : int err= 0; 2237 : : 2238 : 5762690 : + bool is_binlogged= has_binlog_hton(ha_info); 2239 : 5762696 : + if (is_binlogged) 2240 : : { 2241 : 1562080 : if ((err= binlog_commit(thd, all, is_ro_1pc_trans(thd, ha_info, all, 2242 : : is_real_trans)))) 2269 : 6998083 : ha_info_next= ha_info->next(); 2270 : 6998084 : ha_info->reset(); /* keep it conveniently zero-filled */ 2271 : : } 2272 : 5762717 : + DEBUG_SYNC(thd, "commit_handlerton_after"); 2273 : 5762614 : + if (is_binlogged && is_real_trans) 2274 : 608969 : + binlog_post_commit(thd, all); 2275 : 5762622 : trans->ha_list= 0; 2276 : 5762622 : trans->no_2pc=0; 2277 : 5762622 : if (all) 2376 : 1462680 : (void) wsrep_before_rollback(thd, all); 2377 : : #endif /* WITH_WSREP */ 2378 : : 2379 : 1462656 : + bool do_binlog= false; 2380 : 1462656 : if (ha_info) 2381 : : { 2382 : : /* Close all cursors that can not survive ROLLBACK */ 2387 : : { 2388 : : int err; 2389 : 666399 : transaction_participant *ht= ha_info->ht(); 2390 : 666400 : + do_binlog|= (ht == &binlog_tp); 2391 : 666400 : if ((err= ht->rollback(thd, all))) 2392 : : { 2393 : : // cannot happen 2404 : : } 2405 : : #endif /* WITH_WSREP */ 2406 : : } 2407 : 666403 : + DEBUG_SYNC(thd, "rollback_handlerton_after"); 2408 : 666399 : status_var_increment(thd->status_var.ha_rollback_count); 2409 : 666399 : ha_info_next= ha_info->next(); 2410 : 666399 : ha_info->reset(); /* keep it conveniently zero-filled */ 2413 : 646339 : trans->no_2pc=0; 2414 : : } 2415 : : 2416 : 1462655 : + if (do_binlog) 2417 : 21555 : + binlog_post_rollback(thd, all); 2418 : : + 2419 : : #ifdef WITH_WSREP 2420 : 1462713 : if (WSREP(thd) && thd->is_error()) 2421 : : { 2484 : : int result; 2485 : : }; 2486 : : 2487 : 75929 : +static bool xacommit_handlerton(THD *thd, transaction_participant *hton, void *arg) 2488 : : { 2489 : 75929 : if (hton->recover) 2490 : : { 2522 : : 2523 : 16185 : tp_foreach(NULL, commit ? xacommit_handlerton : xarollback_handlerton, &xaop); 2524 : : 2525 : 16187 : + if (commit) 2526 : 6897 : + DEBUG_SYNC(current_thd, "xacommit_handlerton_after"); 2527 : : + else 2528 : 9290 : + DEBUG_SYNC(current_thd, "xarollback_handlerton_after"); 2529 : : + 2530 : 16187 : + if (commit) 2531 : 6897 : + binlog_post_commit_by_xid(xid); 2532 : : + else 2533 : 9290 : + binlog_post_rollback_by_xid(xid); 2534 : 16187 : return xaop.result; 2535 : : } 2536 : : 2648 : : bool error; 2649 : : }; 2650 : : 2651 : : + 2652 : : +/** 2653 : : + Recovery for XID (internal 2pc and user XA) using engine-implemented binlog. 2654 : : + 2655 : : + The binlog provides the state of each XID - prepared, committed, rolled 2656 : : + back. For prepared XA, it also provides the count of the number of engines 2657 : : + participating in that transaction. 2658 : : + 2659 : : + Each XID found prepared in an engine will be committed, rolled back, or left 2660 : : + in prepared state according to the state of the binlog. For an XID in the 2661 : : + prepared state, if the number of engines found having that XID is too 2662 : : + small, it means the server crashed in the middle of preparing a multi- 2663 : : + engine transaction, and that XID will be rolled back both in engines and 2664 : : + in the binlog. 2665 : : +*/ 2666 : : +struct xarecover_engine_binlog 2667 : : +{ 2668 : : + static constexpr uint32_t MAX_HTONS= 32; 2669 : : + 2670 : : + /* Buffer for engines to return their prepared XID into. */ 2671 : : + XID *list; 2672 : : + /* Hash (of handler_binlog_xid_info) of binlog state of XIDs. */ 2673 : : + HASH *xid_hash; 2674 : : + /* 2675 : : + Engine handlertons involved in XID recovery, used for bits in 2676 : : + handler_binlog_xid_info::engine_map. 2677 : : + */ 2678 : : + handlerton *htons[MAX_HTONS]; 2679 : : + /* Used entries in htons. */ 2680 : : + uint32_t num_htons; 2681 : : + /* Size of the XID *list. */ 2682 : : + int len; 2683 : : + /* Set in case of any error during the processing. */ 2684 : : + bool error; 2685 : : +}; 2686 : : + 2687 : : + 2688 : : /** 2689 : : Inserts a new hash member. 2690 : : 2736 : 93 : return member == NULL; 2737 : : } 2738 : : 2739 : : + 2740 : : +static bool 2741 : 24 : +record_hton_for_xid(xarecover_engine_binlog *info, handler_binlog_xid_info *rec, 2742 : : + handlerton *hton) 2743 : : +{ 2744 : : + uint32_t idx; 2745 : 24 : + for (idx= 0; idx < info->num_htons; ++idx) 2746 : : + { 2747 : 8 : + if (info->htons[idx] == hton) 2748 : : + { 2749 : 8 : + rec->engine_map|= 1<num_htons >= xarecover_engine_binlog::MAX_HTONS) 2754 : : + { 2755 : 0 : + sql_print_error("Too many transactional engines during binlog recovery " 2756 : : + "of prepared transactions (max is %u)", 2757 : : + (uint)xarecover_engine_binlog::MAX_HTONS); 2758 : 0 : + return true; 2759 : : + } 2760 : 16 : + rec->engine_map|= 1<num_htons; 2761 : 16 : + info->htons[info->num_htons++]= hton; 2762 : 16 : + return false; 2763 : : +} 2764 : : + 2765 : : + 2766 : 1404 : +static my_bool xarecover_engine_binlog(THD *unused, plugin_ref plugin, 2767 : : + void *arg) 2768 : : +{ 2769 : 1404 : + handlerton *hton= plugin_hton(plugin); 2770 : 1404 : + struct xarecover_engine_binlog *info= 2771 : : + (struct xarecover_engine_binlog *) arg; 2772 : : + int got; 2773 : : + 2774 : 1404 : + if (hton->recover) 2775 : : + { 2776 : 187 : + while ((got= hton->recover(info->list, info->len)) > 0 ) 2777 : : + { 2778 : 20 : + sql_print_information("Found %d prepared transaction(s) in %s", 2779 : 20 : + got, hton_name(hton)->str); 2780 : : + 2781 : 62 : + for (int i=0; i < got; i ++) 2782 : : + { 2783 : 42 : + XID *xid= &info->list[i]; 2784 : 42 : + const uchar *key_ptr= xid->key(); 2785 : 42 : + size_t key_len= xid->key_length(); 2786 : : + handler_binlog_xid_info *rec= (handler_binlog_xid_info *) 2787 : 42 : + my_hash_search(info->xid_hash, key_ptr, key_len); 2788 : : + 2789 : : + /* If the binlog says to roll back, or says nothing, then roll back. */ 2790 : 42 : + if (!rec || rec->xid_state == handler_binlog_xid_info::BINLOG_ROLLBACK) 2791 : : + { 2792 : 12 : + if (hton->rollback_by_xid(info->list+i)) 2793 : 0 : + info->error= true; 2794 : 12 : + continue; 2795 : : + } 2796 : : + 2797 : : + /* If the binlog says to commit, or says nothing, then commit. */ 2798 : 30 : + if (rec->xid_state == handler_binlog_xid_info::BINLOG_COMMIT) 2799 : : + { 2800 : 6 : + if (hton->commit_by_xid(xid)) 2801 : 0 : + info->error= true; 2802 : 6 : + continue; 2803 : : + } 2804 : 24 : + DBUG_ASSERT(rec->xid_state == handler_binlog_xid_info::BINLOG_PREPARE); 2805 : : + 2806 : : + /* 2807 : : + If the binlog has the transaction in the prepared state, then we 2808 : : + must check if all involved engines have it prepared as well. We might 2809 : : + have crashed before all engines had time to (durably) prepare, in 2810 : : + which case we will roll back the ones that did. 2811 : : + So we record in the info->xid_hash that we found the XID in this 2812 : : + engine, and at the end we then check whether to commit or roll back. 2813 : : + */ 2814 : 24 : + DBUG_ASSERT(rec->engine_count > 0); 2815 : 24 : + if (likely(rec->engine_count > 0)) 2816 : 24 : + --rec->engine_count; 2817 : 24 : + if (record_hton_for_xid(info, rec, hton)) 2818 : 0 : + info->error= true; 2819 : : + } 2820 : 20 : + if (got < info->len) 2821 : 20 : + break; 2822 : : + } 2823 : : + } 2824 : 1404 : + return FALSE; 2825 : : +} 2826 : : + 2827 : : + 2828 : : +int 2829 : 169 : +ha_recover_engine_binlog(HASH *xid_hash) 2830 : : +{ 2831 : 169 : + DBUG_ENTER("ha_recover_engine_binlog"); 2832 : 169 : + DBUG_ASSERT(opt_binlog_engine_hton); 2833 : : + struct xarecover_engine_binlog info; 2834 : 169 : + info.xid_hash= xid_hash; 2835 : 169 : + info.num_htons= 0; 2836 : 169 : + info.error= false; 2837 : 169 : + info.list= nullptr; 2838 : : + 2839 : 169 : + sql_print_information("Starting recovery of prepared transactions..."); 2840 : : + 2841 : 169 : + for (info.len= MAX_XID_LIST_SIZE; info.len >= MIN_XID_LIST_SIZE; info.len/=2) 2842 : : + { 2843 : 169 : + info.list=(XID *)my_malloc(key_memory_XID, info.len*sizeof(XID), MYF(0)); 2844 : 169 : + if (likely(info.list)) 2845 : 169 : + break; 2846 : : + } 2847 : 169 : + if (!info.list) 2848 : : + { 2849 : 0 : + sql_print_error(ER(ER_OUTOFMEMORY), 2850 : 0 : + static_cast(info.len*sizeof(XID))); 2851 : 0 : + DBUG_RETURN(1); 2852 : : + } 2853 : : + 2854 : 169 : + plugin_foreach(NULL, xarecover_engine_binlog, 2855 : : + MYSQL_STORAGE_ENGINE_PLUGIN, &info); 2856 : : + 2857 : 169 : + my_free(info.list); 2858 : : + 2859 : 169 : + if (info.error) 2860 : 0 : + DBUG_RETURN(1); 2861 : : + 2862 : : + /* 2863 : : + Now handle any XID found in the prepared state in binlog. They will be 2864 : : + left prepared if all engines that participated in the transaction managed 2865 : : + to prepare them durably before the server restart; otherwise they will be 2866 : : + rolled back in binlog and engines (if any). 2867 : : + */ 2868 : 233 : + for (uint32 i= 0; i < xid_hash->records; ++i) 2869 : : + { 2870 : : + handler_binlog_xid_info *rec= (handler_binlog_xid_info *) 2871 : 64 : + my_hash_element(xid_hash, i); 2872 : 64 : + if (rec->xid_state != handler_binlog_xid_info::BINLOG_PREPARE) 2873 : 36 : + continue; 2874 : 28 : + if (rec->engine_count == 0) 2875 : : + { 2876 : : + /* Recover the XID as a prepared XA transaction. */ 2877 : 24 : + xid_cache_insert(&rec->xid); 2878 : : + } 2879 : : + else 2880 : : + { 2881 : : + /* Not all participating engines prepared, so roll back. */ 2882 : 4 : + void *engine_data= nullptr; 2883 : 4 : + mysql_mutex_lock(&LOCK_commit_ordered); 2884 : 4 : + (*opt_binlog_engine_hton->binlog_xa_rollback_ordered) 2885 : 4 : + (current_thd, &rec->xid, &engine_data); 2886 : 4 : + mysql_mutex_unlock(&LOCK_commit_ordered); 2887 : 4 : + (*opt_binlog_engine_hton->binlog_xa_rollback) 2888 : 4 : + (current_thd, &rec->xid, &engine_data); 2889 : 8 : + for (uint32_t j= 0; j < info.num_htons; ++j) 2890 : : + { 2891 : 4 : + if (rec->engine_map & (1<rollback_by_xid)(&rec->xid); 2894 : : + } 2895 : : + } 2896 : 4 : + (*opt_binlog_engine_hton->binlog_unlog)(&rec->xid, &engine_data); 2897 : 4 : + (*opt_binlog_engine_hton->binlog_oob_free)(engine_data); 2898 : : + } 2899 : : + } 2900 : : + 2901 : 169 : + sql_print_information("Recovery of prepared transaction finished."); 2902 : 169 : + DBUG_RETURN(0); 2903 : 169 : +} 2904 : : + 2905 : : + 2906 : : /* 2907 : : A "transport" type for recovery completion with ha_recover_complete() 2908 : : */ 3164 : 203934 : return FALSE; 3165 : : } 3166 : : 3167 : : + 3168 : 18733 : int ha_recover(HASH *commit_list, MEM_ROOT *arg_mem_root) 3169 : : { 3170 : : struct xarecover_st info; 3176 : 18733 : info.mem_root= arg_mem_root; 3177 : 18733 : info.error= false; 3178 : : 3179 : 18733 : + if (opt_binlog_engine_hton) 3180 : : + { 3181 : 169 : + if (tc_heuristic_recover) 3182 : : + { 3183 : 0 : + sql_print_error("The --tc-heuristic-recover option is not needed with, " 3184 : : + "and cannot be used with --binlog-storage-engine"); 3185 : 0 : + DBUG_RETURN(1); 3186 : : + } 3187 : : + /* 3188 : : + With engine-implemented binlog, recovery is handled during binlog 3189 : : + open, calling into ha_recover_engine_binlog(). 3190 : : + */ 3191 : 169 : + DBUG_ASSERT(!arg_mem_root); 3192 : 169 : + DBUG_RETURN(0); 3193 : : + } 3194 : : + 3195 : : /* commit_list and tc_heuristic_recover cannot be set both */ 3196 : 18564 : DBUG_ASSERT(info.commit_list==0 || tc_heuristic_recover==0); 3197 : : /* if either is set, total_ha_2pc must be set too */ 3204 : 18564 : if (info.commit_list) 3205 : 3658 : sql_print_information("Starting table crash recovery..."); 3206 : : 3207 : 18564 : + for (info.len= MAX_XID_LIST_SIZE; info.len >= MIN_XID_LIST_SIZE; info.len/=2) 3208 : : { 3209 : 18564 : DBUG_EXECUTE_IF("min_xa_len", info.len = 16;); 3210 : 18564 : info.list=(XID *)my_malloc(key_memory_XID, info.len*sizeof(XID), MYF(0)); 3211 : 18564 : + if (likely(info.list)) 3212 : 18564 : + break; 3213 : : } 3214 : 18564 : if (!info.list) 3215 : : { 8890 : 212 : if (!WSREP(bf_thd) && 8891 : 9 : !(bf_thd->variables.wsrep_OSU_method == WSREP_OSU_RSU && 8892 : 0 : wsrep_thd_is_toi(bf_thd))) { 8893 : 9 : DBUG_RETURN(0); 8894 : : } 8895 : : 8901 : : else 8902 : : { 8903 : 0 : WSREP_WARN("Cannot abort InnoDB transaction"); 8904 : : } 8905 : : 8906 : 194 : DBUG_RETURN(0); ===== File: sql/handler.h ===== 33 : : #include "mdl.h" 34 : : #include "ha_handler_stats.h" 35 : : #include "optimizer_costs.h" 36 : : +#include "handler_binlog_reader.h" 37 : : 38 : : #include "sql_analyze_stmt.h" // for Exec_time_tracker 39 : : 55 : : class Field_blob; 56 : : class Column_definition; 57 : : class select_result; 58 : : +class handler_binlog_reader; 59 : : +struct rpl_gtid; 60 : : +struct slave_connection_state; 61 : : +struct rpl_binlog_state_base; 62 : : +struct handler_binlog_event_group_info; 63 : : +struct handler_binlog_purge_info; 64 : : 65 : : // the following is for checking tables 66 : : 930 : 317922 : { return !xid->is_null() && eq(xid->gtrid_length, xid->bqual_length, xid->data); } 931 : 317901 : bool eq(long g, long b, const char *d) const 932 : 317901 : { return !is_null() && g == gtrid_length && b == bqual_length && !memcmp(d, data, g+b); } 933 : 110879 : + void set(const struct xid_t *xid) 934 : 110879 : { memcpy(this, xid, xid->length()); } 935 : 128516 : void set(long f, const char *g, long gl, const char *b, long bl) 936 : : { 978 : 85 : memcpy(&trx_server_id, data+MYSQL_XID_PREFIX_LEN, sizeof(trx_server_id)); 979 : 85 : return trx_server_id; 980 : : } 981 : 110879 : + uint length() const 982 : : { 983 : 110879 : return static_cast(sizeof(formatID)) + key_length(); 984 : : } 1252 : : struct st_mysql_sys_var *var; 1253 : : } ha_create_table_option; 1254 : : 1255 : : + 1256 : : +/* Struct used to return binlog file list for SHOW BINARY LOGS from engine. */ 1257 : : +struct binlog_file_entry 1258 : : +{ 1259 : : + binlog_file_entry *next; 1260 : : + LEX_CSTRING name; 1261 : : + /* The size is filled in by server, engine need not return it. */ 1262 : : + my_off_t size; 1263 : : +}; 1264 : : + 1265 : : + 1266 : : class handler; 1267 : : class group_by_handler; 1268 : : class derived_handler; 1600 : : int (*create_partitioning_metadata)(const char *path, const char *old_path, 1601 : : chf_create_flags action_flag); 1602 : : 1603 : : + /* Optional implementation of binlog in the engine. */ 1604 : : + bool (*binlog_init)(size_t binlog_size, const char *directory, 1605 : : + HASH *recover_xid_hash); 1606 : : + /* Dynamically changing the binlog max size. */ 1607 : : + void (*set_binlog_max_size)(size_t binlog_size); 1608 : : + /* Binlog an event group that doesn't go through commit_ordered. */ 1609 : : + bool (*binlog_write_direct_ordered)(IO_CACHE *cache, 1610 : : + handler_binlog_event_group_info *binlog_info, 1611 : : + const rpl_gtid *gtid); 1612 : : + bool (*binlog_write_direct)(IO_CACHE *cache, 1613 : : + handler_binlog_event_group_info *binlog_info, 1614 : : + const rpl_gtid *gtid); 1615 : : + /* 1616 : : + Called for the last transaction (only) in a binlog group commit, with 1617 : : + no locks being held. 1618 : : + */ 1619 : : + void (*binlog_group_commit_ordered)(THD *thd, 1620 : : + handler_binlog_event_group_info *binlog_info); 1621 : : + /* 1622 : : + Binlog parts of large transactions out-of-band, in different chunks in the 1623 : : + binlog as the transaction executes. This limits the amount of data that 1624 : : + must be binlogged transactionally during COMMIT. The engine_data points to 1625 : : + a pointer location that the engine can set to maintain its own context 1626 : : + for the out-of-band data. 1627 : : + 1628 : : + Optionally savepoints can be set at the point at the start of the write 1629 : : + (ie. before any written data), when stmt_start_data and/or savepoint_data 1630 : : + are non-NULL. Such a point can later be rolled back to by calling 1631 : : + binlog_savepoint_rollback(). (Only) if stmt_start_data or savepoint_data 1632 : : + is non-null can data_len be null (to set savepoint(s) and do nothing else). 1633 : : + */ 1634 : : + bool (*binlog_oob_data_ordered)(THD *thd, const unsigned char *data, 1635 : : + size_t data_len, void **engine_data, 1636 : : + void **stmt_start_data, 1637 : : + void **savepoint_data); 1638 : : + bool (*binlog_oob_data)(THD *thd, const unsigned char *data, 1639 : : + size_t data_len, void **engine_data); 1640 : : + /* 1641 : : + Rollback to a prior point in out-of-band binlogged partial transaction 1642 : : + data, for savepoint support. The stmt_start_data and/or savepoint_data, 1643 : : + if non-NULL, correspond to the point set by an earlier binlog_oob_data() 1644 : : + call. 1645 : : + 1646 : : + Exactly one of stmt_start_data or savepoint_data will be non-NULL, 1647 : : + corresponding to either rolling back to the start of the current statement, 1648 : : + or to an earlier set savepoint. 1649 : : + */ 1650 : : + void (*binlog_savepoint_rollback)(THD *thd, void **engine_data, 1651 : : + void **stmt_start_data, 1652 : : + void **savepoint_data); 1653 : : + /* 1654 : : + Call to reset (for new transactions) the engine_data from 1655 : : + binlog_oob_data(). Can also change the pointer to point to different data 1656 : : + (or set it to NULL). 1657 : : + */ 1658 : : + void (*binlog_oob_reset)(void **engine_data); 1659 : : + /* Call to allow engine to release the engine_data from binlog_oob_data(). */ 1660 : : + void (*binlog_oob_free)(void *engine_data); 1661 : : + /* 1662 : : + Durably persist the event data for the current user-XA transaction, 1663 : : + identified by XID. 1664 : : + 1665 : : + This way, a later XA COMMIT can then be binlogged correctly with the 1666 : : + persisted event data, even across server restart. 1667 : : + 1668 : : + The ENGINE_COUNT is the number of storage engines that participate in the 1669 : : + XA transaction. This is used to correctly handle crash recovery if the 1670 : : + server crashed in the middle of XA PREPARE. If during crash recovery, 1671 : : + we find the XID present in less than ENGINE_COUNT engines, then the 1672 : : + XA PREPARE did not complete before the crash, and should be rolled back 1673 : : + during crash recovery. 1674 : : + */ 1675 : : + /* Binlog an event group that doesn't go through commit_ordered. */ 1676 : : + bool (*binlog_write_xa_prepare_ordered)(THD *thd, 1677 : : + handler_binlog_event_group_info *binlog_info, uchar engine_count); 1678 : : + bool (*binlog_write_xa_prepare)(THD *thd, 1679 : : + handler_binlog_event_group_info *binlog_info, uchar engine_count); 1680 : : + /* 1681 : : + Binlog rollback a transaction that was previously made durably prepared 1682 : : + with binlog_write_xa_prepare. 1683 : : + */ 1684 : : + bool (*binlog_xa_rollback_ordered)(THD *thd, const XID *xid, 1685 : : + void **engine_data); 1686 : : + bool (*binlog_xa_rollback)(THD *thd, const XID *xid, void **engine_data); 1687 : : + /* 1688 : : + The "unlog" method is used after a commit with an XID - either internal 1689 : : + 2-phase commit with a separate storage engine, or explicit user 1690 : : + XA COMMIT. For user XA, it is also used after XA ROLLBACK. 1691 : : + 1692 : : + The binlog first writes the commit durably, then the engines commit 1693 : : + durably, and finally "unlog" is done. The binlog engine must ensure it 1694 : : + can recover the committed XID until unlog has been called, after which 1695 : : + point resources can be freed, binlog files purged, etc. 1696 : : + */ 1697 : : + void (*binlog_unlog)(const XID *xid, void **engine_data); 1698 : : + /* 1699 : : + Obtain an object to allow reading from the binlog. 1700 : : + The boolean argument wait_durable is set to true to require that 1701 : : + transactions be durable before they can be read and returned from the 1702 : : + reader. This is used to make replication crash-safe without requiring 1703 : : + durability; this way, if the master crashes, when it comes back up the 1704 : : + slave will not be ahead and replication will not diverge. 1705 : : + */ 1706 : : + handler_binlog_reader * (*get_binlog_reader)(bool wait_durable); 1707 : : + /* 1708 : : + Obtain the current position in the binlog. 1709 : : + Used to support legacy SHOW MASTER STATUS. 1710 : : + */ 1711 : : + void (*binlog_status)(uint64_t * out_fileno, uint64_t *out_pos); 1712 : : + /* Get a binlog name from a file_no. */ 1713 : : + void (*get_filename)(char name[FN_REFLEN], uint64_t file_no); 1714 : : + /* Obtain list of binlog files (SHOW BINARY LOGS). */ 1715 : : + binlog_file_entry * (*get_binlog_file_list)(MEM_ROOT *mem_root); 1716 : : + /* 1717 : : + End the current binlog file, and create and switch to a new one. 1718 : : + Used to implement FLUSH BINARY LOGS. 1719 : : + */ 1720 : : + bool (*binlog_flush)(); 1721 : : + /* 1722 : : + Read the binlog state at the start of the very first (not purged) binlog 1723 : : + file, and return it in *out_state. This is used to check validity of 1724 : : + FLUSH BINARY LOGS DELETE_DOMAIN_ID=(). 1725 : : + 1726 : : + Returns true on error, false on ok. 1727 : : + */ 1728 : : + bool (*binlog_get_init_state)(rpl_binlog_state_base *out_state); 1729 : : + /* Engine implementation of RESET MASTER. */ 1730 : : + bool (*reset_binlogs)(); 1731 : : + /* 1732 : : + Engine implementation of PURGE BINARY LOGS. 1733 : : + Return 0 for ok or one of LOG_INFO_* errors. 1734 : : + 1735 : : + See also ha_binlog_purge_info() for auto-purge. 1736 : : + */ 1737 : : + int (*binlog_purge)(handler_binlog_purge_info *purge_info); 1738 : : + 1739 : : /********************************************************************** 1740 : : Functions to intercept queries 1741 : : **********************************************************************/ 5884 : : int ha_commit_trans(THD *thd, bool all); 5885 : : int ha_rollback_trans(THD *thd, bool all); 5886 : : int ha_prepare(THD *thd); 5887 : : +int ha_recover_engine_binlog(HASH *xid_hash); 5888 : : int ha_recover(HASH *commit_list, MEM_ROOT *mem_root= NULL); 5889 : : uint ha_recover_complete(HASH *commit_list, Binlog_offset *coord= NULL); 5890 : : 6022 : : #ifndef DBUG_OFF 6023 : : String dbug_format_row(TABLE *table, const uchar *rec, bool print_names= true); 6024 : : #endif /* DBUG_OFF */ 6025 : : + 6026 : : +/* Struct with info about an event group to be binlogged by a storage engine. */ 6027 : : +struct handler_binlog_event_group_info { 6028 : : + /* 6029 : : + These are returned by (set by) the binlog_write_direct_ordered hton 6030 : : + method to approximate/best-effort position of the start of where the 6031 : : + event group was written. 6032 : : + */ 6033 : : + uint64_t out_file_no; 6034 : : + uint64_t out_offset; 6035 : : + /* Opaque pointer for the engine's use. */ 6036 : : + void *engine_ptr; 6037 : : + /* 6038 : : + Secondary engine context ptr. 6039 : : + This will be non-null only when both non-transactional (aka statement cache) 6040 : : + and transactional (aka transaction cache) updates are binlogged together. 6041 : : + Then this secondary pointer is the non-transactional / statement cache 6042 : : + part, and it should be considered to go before the transactional / 6043 : : + transaction cache part in the commit record. 6044 : : + */ 6045 : : + void *engine_ptr2; 6046 : : + /* 6047 : : + The XID for XA PREPARE/XA COMMIT; else NULL. 6048 : : + When this is set, the IO_CACHE only contains the GTID. All other event data 6049 : : + was spilled as OOB and persisted with the binlog_write_xa_prepare hton 6050 : : + call; the engine binlog implementation must use the XID to look up or 6051 : : + otherwise refer to that OOB data. 6052 : : + */ 6053 : : + const XID *xa_xid; 6054 : : + /* End of data that has already been binlogged out-of-band. */ 6055 : : + my_off_t out_of_band_offset; 6056 : : + /* 6057 : : + Offset of the GTID event, which comes first in the event group, but is put 6058 : : + at the end of the IO_CACHE containing the data to be binlogged. 6059 : : + */ 6060 : : + my_off_t gtid_offset; 6061 : : + /* 6062 : : + If xa_xid is non-NULL, this is set for an internal 2-phase commit between 6063 : : + the engine binlog and one or more additional storage engines participating 6064 : : + in the transaction. In this case, there is no call to the 6065 : : + binlog_write_xa_prepare() method. The binlog engine must record durably 6066 : : + that the xa_xid was committed, and in case of recovery it must pass the 6067 : : + xa_xid to the server layer for it to commit in all participating engines. 6068 : : + 6069 : : + If not set, any XID is user external XA, and the xa_xid was previously 6070 : : + passed to binlog_write_xa_prepare(). The binlog engine must again record 6071 : : + durably that the xa_xid was committed and recover it in case of crash. 6072 : : + 6073 : : + The ability to recover the xa_xid must remain until the binlog_xa_unlog() 6074 : : + method is called. 6075 : : + */ 6076 : : + bool internal_xa; 6077 : : +}; 6078 : : + 6079 : : + 6080 : : +/* Structure returned by ha_binlog_purge_info(). */ 6081 : : +struct handler_binlog_purge_info { 6082 : : + /* The earliest binlog file that is in use by a dump thread. */ 6083 : : + uint64_t limit_file_no; 6084 : : + /* 6085 : : + Set by engine to give a reason why a requested purge could not be done. 6086 : : + If set, then nonpurge_filename should be set to the filename. 6087 : : + 6088 : : + Also set by ha_binlog_purge_info() when it returns false, to the reason 6089 : : + why no purge is possible. In this case, the nonpurge_filename is set 6090 : : + to the empty string. 6091 : : + */ 6092 : : + const char *nonpurge_reason; 6093 : : + /* The user-configured maximum total size of the binlog. */ 6094 : : + ulonglong limit_size; 6095 : : + /* Binlog name, for PURGE BINARY LOGS TO. */ 6096 : : + const char *limit_name; 6097 : : + /* The earliest file date (unix timestamp) that should not be purged. */ 6098 : : + time_t limit_date; 6099 : : + /* Whether purge by date and/or by size and/or name is requested. */ 6100 : : + bool purge_by_date, purge_by_size, purge_by_name; 6101 : : + /* 6102 : : + The name of the file that could not be purged, when nonpurge_reason 6103 : : + is given. 6104 : : + */ 6105 : : + char nonpurge_filename[FN_REFLEN]; 6106 : : + /* Default constructor to silence compiler warnings -Wuninitialized. */ 6107 : : + handler_binlog_purge_info()= default; 6108 : : +}; 6109 : : + 6110 : : + 6111 : : +/* 6112 : : + Structure holding information about each XID present in binlog engine at 6113 : : + server startup. 6114 : : + 6115 : : + Objects of this class (or a class derived from it by the engine binlog 6116 : : + implementation) will be inserted into a HASH passed to the binlog_init 6117 : : + hton call. The server layer will free these objects using normal delete. 6118 : : +*/ 6119 : : +class handler_binlog_xid_info { 6120 : : +public: 6121 : : + enum binlog_xid_state { 6122 : : + BINLOG_PREPARE, BINLOG_COMMIT, BINLOG_ROLLBACK 6123 : : + }; 6124 : : + XID xid; 6125 : : + /* 6126 : : + Number of storage engines in which this transaction is prepared. Used when 6127 : : + xid_state==BINLOG_PREPARE. 6128 : : + 6129 : : + This is used to correctly recover from a crash in the middle of an XA 6130 : : + PREPARE. If the crash happens before all engines had time to durably 6131 : : + prepare, then the XID will be rolled back. If all engines got prepared, 6132 : : + then the XID will be preserved in "prepared" state. 6133 : : + */ 6134 : : + uint32_t engine_count; 6135 : : + /* Bitmap of which engine(s) a prepared transaction was found in. */ 6136 : : + uint32_t engine_map; 6137 : : + enum binlog_xid_state xid_state; 6138 : : + 6139 : : + /* The key function to use for the HASH. */ 6140 : 92 : + static const uchar *get_key(const void *p, size_t *out_len, my_bool) 6141 : : + { 6142 : 92 : + const XID *xid= 6143 : : + &(reinterpret_cast(p)->xid); 6144 : 92 : + *out_len= xid->key_length(); 6145 : 92 : + return xid->key(); 6146 : : + } 6147 : 92 : + handler_binlog_xid_info(binlog_xid_state typ) : 6148 : 92 : + engine_count(0), engine_map(0), xid_state(typ) { } 6149 : 92 : + virtual ~handler_binlog_xid_info() { }; 6150 : : +}; 6151 : : + 6152 : : + 6153 : : #endif /* HANDLER_INCLUDED */ ===== File: sql/item_func.cc ===== 4035 : 18 : sql_print_information("Could not get master info for %s", connection_name.str); 4036 : 18 : goto err; 4037 : : } 4038 : : + 4039 : 33738 : + if (mi->binlog_storage_engine) 4040 : : + { 4041 : 3 : + my_error(ER_NOT_AVAILABLE_WITH_ENGINE_BINLOG, MYF(0), "master_pos_wait()"); 4042 : 3 : + mi->release(); 4043 : 3 : + goto err; 4044 : : + } 4045 : 33735 : if ((event_count = mi->rli.wait_for_pos(thd, log_name, pos, timeout)) == -2) 4046 : : { 4047 : 33 : null_value = 1; ===== File: sql/item_strfunc.cc ===== 3641 : 6846 : String name_str, *name; 3642 : : longlong pos; 3643 : : 3644 : 6846 : + if (opt_binlog_engine_hton) 3645 : : + { 3646 : 3 : + my_error(ER_NOT_AVAILABLE_WITH_ENGINE_BINLOG, MYF(0), "BINLOG_GTID_POS()"); 3647 : 3 : + goto err; 3648 : : + } 3649 : : + 3650 : 6843 : name= args[0]->val_str(&name_str); 3651 : 6843 : pos= args[1]->val_int(); 3652 : : ===== File: sql/log.cc ===== 94 : : static int binlog_init(void *p); 95 : : static int binlog_close_connection(THD *thd); 96 : : static int binlog_savepoint_set(THD *thd, void *sv); 97 : : +static int binlog_savepoint_release(THD *thd, void *sv); 98 : : static int binlog_savepoint_rollback(THD *thd, void *sv); 99 : : static bool binlog_savepoint_rollback_can_release_mdl(THD *thd); 100 : : static int binlog_rollback(THD *thd, bool all); 103 : : static int binlog_flush_cache(THD *thd, binlog_cache_mngr *cache_mngr, 104 : : Log_event *end_ev, bool all, bool using_stmt, 105 : : bool using_trx, bool is_ro_1pc); 106 : : +static int binlog_spill_to_engine(struct st_io_cache *cache, const uchar *data, 107 : : + size_t len); 108 : : 109 : : static const LEX_CSTRING write_error_msg= 110 : : { STRING_WITH_LEN("error writing to the binary log") }; 137 : : static ulonglong binlog_status_group_commit_trigger_timeout; 138 : : static char binlog_snapshot_file[FN_REFLEN]; 139 : : static ulonglong binlog_snapshot_position; 140 : : +static constexpr size_t BINLOG_SPILL_MAX= 512 * 1024; 141 : : +static size_t binlog_max_spill_size; 142 : : 143 : : static const char *fatal_log_error= 144 : : "Could not use %s for logging (error %d). " 233 : 27690 : void set_reserved_bytes(uint32 header_len) 234 : : { 235 : : // Add reserved space for gtid event 236 : 27690 : + header_len+= LOG_EVENT_HEADER_LEN + Gtid_log_event::max_size + 237 : : BINLOG_CHECKSUM_LEN; 238 : : 239 : : // reserved size is aligned to IO_SIZE. 288 : 2566 : return binlog_commit_by_rotate.get_reserved_size(); 289 : : } 290 : : 291 : : + 292 : : +/* 293 : : + This struct, for --binlog-storage-engine=ENGINE, keeps track of savepoints 294 : : + set in the current transaction that are still within the in-memory trx 295 : : + cache (not yet spilled as out-of-band data into the binlog). 296 : : +*/ 297 : : +struct binlog_savepoint_info { 298 : : + binlog_savepoint_info *next; 299 : : + void *engine_ptr; 300 : : + my_off_t cache_offset; 301 : : +}; 302 : : + 303 : : + 304 : : /* 305 : : Variables for the binlog background thread. 306 : : Protected by the MYSQL_BIN_LOG::LOCK_binlog_background_thread mutex. 496 : : 497 : : class binlog_cache_mngr { 498 : : public: 499 : 63412 : + binlog_cache_mngr(THD *thd_arg, 500 : : + my_off_t param_max_binlog_stmt_cache_size, 501 : : my_off_t param_max_binlog_cache_size, 502 : : ulong *param_ptr_binlog_stmt_cache_use, 503 : : ulong *param_ptr_binlog_stmt_cache_disk_use, 504 : : ulong *param_ptr_binlog_cache_use, 505 : : ulong *param_ptr_binlog_cache_disk_use, 506 : : bool precompute_checksums) 507 : 63412 : + : thd(thd_arg), 508 : 63412 : + stmt_cache(false, precompute_checksums), 509 : 63412 : + trx_cache(true, precompute_checksums), 510 : 63413 : + last_commit_pos_offset(0), 511 : 63413 : + stmt_start_engine_ptr(nullptr), 512 : 63413 : + cache_savepoint_list(nullptr), 513 : 63413 : + cache_savepoint_next_ptr(&cache_savepoint_list), 514 : 63413 : + using_stmt_cache(FALSE), using_trx_cache(FALSE), 515 : 63413 : + using_xa(FALSE), xa_xid(0), 516 : 63413 : + engine_binlogged(FALSE), need_write_direct(FALSE) 517 : : { 518 : 63413 : stmt_cache.set_binlog_cache_info(param_max_binlog_stmt_cache_size, 519 : : param_ptr_binlog_stmt_cache_use, 521 : 63411 : trx_cache.set_binlog_cache_info(param_max_binlog_cache_size, 522 : : param_ptr_binlog_cache_use, 523 : : param_ptr_binlog_cache_disk_use); 524 : 63411 : + if (opt_binlog_engine_hton) 525 : 1971 : + last_commit_pos_file.engine_file_no= ~(uint64_t)0; 526 : : + else 527 : 61440 : + last_commit_pos_file.legacy_name[0]= 0; 528 : 63411 : + } 529 : 60657 : + ~binlog_cache_mngr() 530 : : + { 531 : 60657 : } 532 : : 533 : 1296243 : void reset(bool do_stmt, bool do_trx) 534 : : { 535 : 1296243 : if (do_stmt) 536 : : + { 537 : 557786 : + if (opt_binlog_engine_hton) 538 : : + { 539 : 140428 : + stmt_cache.reset_for_engine_binlog(); 540 : : + /* 541 : : + Use a custom write_function to spill to the engine-implemented binlog. 542 : : + And re-use the IO_CACHE::append_read_pos as a handle for our 543 : : + write_function; it is unused when the cache is not SEQ_READ_APPEND. 544 : : + */ 545 : 140428 : + stmt_cache.cache_log.write_function= binlog_spill_to_engine; 546 : 140428 : + stmt_cache.cache_log.append_read_pos= (uchar *)this; 547 : : + } 548 : : + else 549 : 417358 : + stmt_cache.reset(); 550 : 557783 : + using_stmt_cache= FALSE; 551 : : + } 552 : 1296240 : if (do_trx) 553 : : { 554 : 1163721 : + if (opt_binlog_engine_hton) 555 : : + { 556 : 273910 : + trx_cache.reset_for_engine_binlog(); 557 : 273895 : + trx_cache.cache_log.write_function= binlog_spill_to_engine; 558 : 273895 : + trx_cache.cache_log.append_read_pos= (uchar *)this; 559 : 273895 : + last_commit_pos_file.engine_file_no= ~(uint64_t)0; 560 : 273895 : + stmt_start_engine_ptr= nullptr; 561 : 273895 : + cache_savepoint_list= nullptr; 562 : 273895 : + cache_savepoint_next_ptr= &cache_savepoint_list; 563 : : + } 564 : : + else 565 : : + { 566 : 889811 : + trx_cache.reset(); 567 : 889789 : + last_commit_pos_file.legacy_name[0]= 0; 568 : : + } 569 : 1163684 : last_commit_pos_offset= 0; 570 : 1163684 : + using_trx_cache= FALSE; 571 : 1163684 : + using_xa= FALSE; 572 : : } 573 : 1296203 : + engine_binlogged= FALSE; 574 : 1296203 : + need_write_direct= FALSE; 575 : : + /* 576 : : + need_engine_2pc is not reset here, as we need it still, at the end of 577 : : + MYSQL_LOG_BIN::log_and_order() where it will be reset. 578 : : + */ 579 : 1296203 : } 580 : : 581 : 16174909 : binlog_cache_data* get_binlog_cache_data(bool is_transactional) 583 : 16174909 : return (is_transactional ? &trx_cache : &stmt_cache); 584 : : } 585 : : 586 : : + /* 587 : : + The cache_data to use when binlogging into the --binlog-storage-engine. 588 : : + 589 : : + With binlog in storage engine, we're optimizing for transactional 590 : : + event groups, and for simplicity we only pass a single cache into the 591 : : + engine binlog implementation. 592 : : + 593 : : + When mixing transactional and non-transactional updates in a single event 594 : : + group, we flush everything as out-of-band-data, and use the transaction 595 : : + cache just for the GTID. 596 : : + 597 : : + The special case comes when we are using both the transactional and 598 : : + the statement cache, _but_ the transaction cache happens to be empty. 599 : : + Then we need to put the GTID in the statement cache and pass that to 600 : : + the engine. 601 : : + */ 602 : 269750 : + binlog_cache_data *engine_cache_data() 603 : : + { 604 : 269084 : + return ( unlikely(!using_trx_cache) || ( unlikely(using_stmt_cache) && 605 : 268094 : + !stmt_cache.empty() && 606 : 538834 : + trx_cache.empty() ) ) ? 607 : 269750 : + &stmt_cache : &trx_cache; 608 : : + } 609 : : + 610 : 2414403 : IO_CACHE* get_binlog_cache_log(bool is_transactional) 611 : : { 612 : 2414403 : return (is_transactional ? &trx_cache.cache_log : &stmt_cache.cache_log); 613 : : } 614 : : 615 : : + THD *thd; 616 : : + 617 : : binlog_cache_data stmt_cache; 618 : : 619 : : binlog_cache_data trx_cache; 620 : : 621 : : + /* Buffer used to pass internal my_xid into engine as struct xid_t. */ 622 : : + XID xid_buf; 623 : : + 624 : : /* 625 : : Binlog position for current transaction. 626 : : For START TRANSACTION WITH CONSISTENT SNAPSHOT, this is the binlog 627 : : position corresponding to the snapshot taken. During (and after) commit, 628 : : this is set to the binlog position corresponding to just after the 629 : : commit (so storage engines can store it in their transaction log). 630 : : + 631 : : + For the legacy binlog, we have to use the full filename, for binlog 632 : : + implemented in engine we can just keep track of the file number. 633 : : + */ 634 : : + union { 635 : : + uint64_t engine_file_no; 636 : : + char legacy_name[FN_REFLEN]; 637 : : + } last_commit_pos_file; 638 : : + uint64_t last_commit_pos_offset; 639 : : + 640 : : + /* Engine data pointer for start-of-statement savepoint. */ 641 : : + void *stmt_start_engine_ptr; 642 : : + /* 643 : : + List of pending savepoints still in the trx cache (for engine-implemented 644 : : + binlogging). 645 : : */ 646 : : + binlog_savepoint_info *cache_savepoint_list; 647 : : + binlog_savepoint_info **cache_savepoint_next_ptr; 648 : : 649 : : + /* 650 : : + Set from binlog_flush_cache(), to mark if we are flushing the stmt cache 651 : : + or the trx cache (or both). 652 : : + */ 653 : : + bool using_stmt_cache; 654 : : + bool using_trx_cache; 655 : : /* 656 : : Flag set true if this transaction is committed with log_xid() as part of 657 : : XA, false if not. 658 : : */ 659 : : bool using_xa; 660 : : my_xid xa_xid; 661 : : + /* 662 : : + Set true when not using --binlog-storage-engine and we need to decrement 663 : : + the xid_list reference count for the transaction at unlog time. (The 664 : : + xid_list refcounting is used to keep binlog files for recovery while 665 : : + transactions may still be in the prepared state). 666 : : + */ 667 : : bool need_unlog; 668 : : + /* 669 : : + Set true when binlog engine fetches the cache data with binlog_get_cache() 670 : : + and does the binlogging for us. 671 : : + */ 672 : : + bool engine_binlogged; 673 : : + /* 674 : : + Set when we called binlog_write_direct_ordered() during binlog group commit 675 : : + (due to engine_binlogged==false) and need to call binlog_write_direct() 676 : : + later after releasing mutex. 677 : : + */ 678 : : + bool need_write_direct; 679 : : /* 680 : : Id of binlog that transaction was written to; only needed if need_unlog is 681 : : true. 682 : : */ 683 : : + /* 684 : : + Set when using --binlog-storage-engine, but there is another XA-capable 685 : : + engine involved in the transaction, so that we need to do 2-phase commit 686 : : + to ensure consistency in case of crash. 687 : : + */ 688 : : + bool need_engine_2pc; 689 : : ulong binlog_id; 690 : : /* Set if we get an error during commit that must be returned from unlog(). */ 691 : : bool delayed_error; 1899 : : */ 1900 : : 1901 : : static void 1902 : 1188160 : +binlog_trans_log_savepos(THD *thd, binlog_cache_mngr *cache_mngr, my_off_t *pos) 1903 : : { 1904 : 1188160 : DBUG_ENTER("binlog_trans_log_savepos"); 1905 : : +// DBUG_ASSERT(!opt_binlog_engine_hton); 1906 : 1188168 : DBUG_ASSERT(pos != NULL); 1907 : 1188168 : DBUG_ASSERT((WSREP(thd) && wsrep_emulate_bin_log) || mysql_bin_log.is_open()); 1908 : 1188171 : *pos= cache_mngr->trx_cache.get_byte_position(); 1909 : 1188175 : DBUG_PRINT("return", ("*pos: %lu", (ulong) *pos)); 1927 : : 1928 : : */ 1929 : : static void 1930 : 1209 : +binlog_trans_log_truncate(THD *thd, binlog_savepoint_info *sv) 1931 : : { 1932 : 1209 : DBUG_ENTER("binlog_trans_log_truncate"); 1933 : 1209 : + my_off_t pos= sv->cache_offset; 1934 : 1209 : DBUG_PRINT("enter", ("pos: %lu", (ulong) pos)); 1935 : : 1936 : 1209 : DBUG_ASSERT(thd->binlog_get_cache_mngr() != NULL); 1938 : 1209 : DBUG_ASSERT(pos != ~(my_off_t) 0); 1939 : : 1940 : 1209 : binlog_cache_mngr *const cache_mngr= thd->binlog_get_cache_mngr(); 1941 : 1209 : + binlog_cache_data *trx_cache= &cache_mngr->trx_cache; 1942 : 1209 : + if (!opt_binlog_engine_hton) 1943 : : + { 1944 : 135 : + trx_cache->restore_savepoint(pos); 1945 : 135 : + DBUG_VOID_RETURN; 1946 : : + } 1947 : : + 1948 : : + /* 1949 : : + If the savepoint is still in the trx cache, then we can simply truncate 1950 : : + the cache. 1951 : : + If the savepoint was spilled as oob data, then we need to call into the 1952 : : + engine binlog to have it discard the to-be-rolled-back binlog data. 1953 : : + */ 1954 : 1074 : + IO_CACHE *cache= &trx_cache->cache_log; 1955 : 1074 : + if (pos >= cache->pos_in_file) 1956 : : + { 1957 : 406 : + trx_cache->restore_savepoint(pos); 1958 : 406 : + trx_cache->cache_log.write_function= binlog_spill_to_engine; 1959 : : + /* Remove any later in-cache savepoints. */ 1960 : 406 : + binlog_savepoint_info *sp= cache_mngr->cache_savepoint_list; 1961 : 460 : + while (sp) 1962 : : + { 1963 : 458 : + if (sp == sv) 1964 : : + { 1965 : 404 : + sp->next= nullptr; /* Drop the tail of the list. */ 1966 : 404 : + cache_mngr->cache_savepoint_next_ptr= &sp->next; 1967 : 404 : + break; 1968 : : + } 1969 : 54 : + sp= sp->next; 1970 : : + } 1971 : : + /* 1972 : : + If the savepoint is at the start of the cache, then it might have been 1973 : : + already spilled to the engine binlog, then rolled back to (which would 1974 : : + leave the cache truncated to the point of that savepoint). 1975 : : + 1976 : : + But otherwise, the savepoint is pending to be spilled to engine if 1977 : : + needed, and should be found in the list. 1978 : : + */ 1979 : 406 : + DBUG_ASSERT(pos == cache->pos_in_file || sp != nullptr); 1980 : : + 1981 : 406 : + DBUG_VOID_RETURN; 1982 : : + } 1983 : : + 1984 : : + /* 1985 : : + Truncate what's in the cache, then call into the engine to rollback to 1986 : : + the prior set savepoint. 1987 : : + */ 1988 : 668 : + trx_cache->restore_savepoint(cache->pos_in_file); 1989 : 668 : + trx_cache->reset_cache_for_engine(pos, binlog_spill_to_engine); 1990 : : + /* No pending savepoints in-cache anymore. */ 1991 : 668 : + cache_mngr->cache_savepoint_next_ptr= &cache_mngr->cache_savepoint_list; 1992 : 668 : + cache_mngr->cache_savepoint_list= nullptr; 1993 : 668 : + cache_mngr->trx_cache.engine_binlog_info.out_of_band_offset= sv->cache_offset; 1994 : 668 : + (*opt_binlog_engine_hton->binlog_savepoint_rollback) 1995 : 668 : + (thd, &cache_mngr->trx_cache.engine_binlog_info.engine_ptr, 1996 : : + nullptr, &sv->engine_ptr); 1997 : 668 : DBUG_VOID_RETURN; 1998 : 1209 : } 1999 : : 2002 : 15107 : int binlog_init(void *p) 2003 : : { 2004 : 15107 : bzero(&binlog_tp, sizeof(binlog_tp)); 2005 : 15107 : + binlog_tp.savepoint_offset= sizeof(binlog_savepoint_info); 2006 : 15107 : binlog_tp.close_connection= binlog_close_connection; 2007 : 15107 : binlog_tp.savepoint_set= binlog_savepoint_set; 2008 : 15107 : + binlog_tp.savepoint_release= binlog_savepoint_release; 2009 : 15107 : binlog_tp.savepoint_rollback= binlog_savepoint_rollback; 2010 : 15107 : binlog_tp.savepoint_rollback_can_release_mdl= 2011 : : binlog_savepoint_rollback_can_release_mdl; 2088 : 675274 : DBUG_ENTER("binlog_flush_cache"); 2089 : 675276 : DBUG_PRINT("enter", ("end_ev: %p", end_ev)); 2090 : : 2091 : 675271 : + bool doing_stmt= using_stmt && !cache_mngr->stmt_cache.empty(); 2092 : 675271 : + bool doing_trx= using_trx && !cache_mngr->trx_cache.empty(); 2093 : 675267 : + if (doing_stmt || doing_trx || thd->transaction->xid_state.is_explicit_XA()) 2094 : : { 2095 : 675264 : if (using_stmt && thd->binlog_flush_pending_rows_event(TRUE, FALSE)) 2096 : 0 : DBUG_RETURN(1); 2107 : : } 2108 : : #endif /* WITH_WSREP */ 2109 : : 2110 : 675262 : + if (opt_binlog_engine_hton && 2111 : 136523 : + likely(!(thd->lex->sql_command == SQLCOM_XA_COMMIT && 2112 : : + thd->lex->xa_opt != XA_ONE_PHASE))) 2113 : : + { 2114 : : + /* 2115 : : + Write the end_event into the cache, in preparation for sending the 2116 : : + cache to the engine to be binlogged as a whole. 2117 : : + 2118 : : + Except for user XA COMMIT, where we already wrote the end event into 2119 : : + the OOB data that was persisted in the binlog. 2120 : : + */ 2121 : : + binlog_cache_data *cache_data; 2122 : 136495 : + if (doing_trx || !doing_stmt) 2123 : : + { 2124 : 136271 : + end_ev->cache_type= Log_event::EVENT_TRANSACTIONAL_CACHE; 2125 : 136271 : + cache_data= &cache_mngr->trx_cache; 2126 : : + } 2127 : : + else 2128 : : + { 2129 : 224 : + end_ev->cache_type= Log_event::EVENT_STMT_CACHE; 2130 : 224 : + cache_data= &cache_mngr->stmt_cache; 2131 : : + } 2132 : 136495 : + if (mysql_bin_log.write_event(end_ev, cache_data, &cache_data->cache_log)) 2133 : 0 : + DBUG_RETURN(1); 2134 : : + 2135 : 136493 : + if (cache_data->engine_binlog_info.out_of_band_offset) 2136 : : + { 2137 : : + /* 2138 : : + This is a "large" transaction, where parts of the transaction were 2139 : : + already binlogged out-of-band to the engine binlog. 2140 : : + 2141 : : + Binlog the remaining bits of event data as well, so all the event 2142 : : + group is consecutive out-of-band data and the commit record will 2143 : : + only contain the GTID event (depending on engine implementation). 2144 : : + */ 2145 : 694 : + if (my_b_flush_io_cache(&cache_data->cache_log, 0)) 2146 : 0 : + DBUG_RETURN(1); 2147 : : + } 2148 : : + } 2149 : : + 2150 : : /* 2151 : : Doing a commit or a rollback including non-transactional tables, 2152 : : i.e., ending a transaction where we might write the transaction 2184 : 675258 : } 2185 : : 2186 : : 2187 : : +static void 2188 : 134581 : +binlog_setup_engine_commit_data(handler_binlog_event_group_info *context, 2189 : : + binlog_cache_mngr *cache_mngr) 2190 : : +{ 2191 : 134581 : + if (unlikely(context->xa_xid)) 2192 : : + { 2193 : : + /* Mark that we are doing XA and need to unlog. */ 2194 : 28 : + cache_mngr->need_engine_2pc= true; 2195 : 28 : + context->internal_xa= false; 2196 : : + } 2197 : 134553 : + else if (unlikely(cache_mngr->need_engine_2pc)) 2198 : : + { 2199 : : + /* Internal 2-phase with multiple xa-capable engines. */ 2200 : 40 : + DBUG_ASSERT(cache_mngr->xa_xid != 0); 2201 : 40 : + cache_mngr->xid_buf.set(cache_mngr->xa_xid); 2202 : 40 : + context->xa_xid= &cache_mngr->xid_buf; 2203 : 40 : + context->internal_xa= true; 2204 : : + } 2205 : 134581 : +} 2206 : : + 2207 : : + 2208 : : +extern "C" 2209 : : +void 2210 : 390981 : +binlog_get_cache(THD *thd, uint64_t file_no, uint64_t offset, 2211 : : + IO_CACHE **out_cache, 2212 : : + handler_binlog_event_group_info **out_context, 2213 : : + const rpl_gtid **out_gtid) 2214 : : +{ 2215 : 390981 : + IO_CACHE *cache= nullptr; 2216 : 390981 : + handler_binlog_event_group_info *context= nullptr; 2217 : : + binlog_cache_mngr *cache_mngr; 2218 : 390981 : + const rpl_gtid *gtid= nullptr; 2219 : : + /* opt_binlog_engine_hton can be unset during bootstrap. */ 2220 : 524974 : + if (likely(opt_binlog_engine_hton) && 2221 : 133993 : + (cache_mngr= thd->binlog_get_cache_mngr())) 2222 : : + { 2223 : 133993 : + cache_mngr->engine_binlogged= TRUE; 2224 : 133993 : + cache_mngr->last_commit_pos_file.engine_file_no= file_no; 2225 : 133993 : + cache_mngr->last_commit_pos_offset= offset; 2226 : 133993 : + binlog_cache_data *cache_data= cache_mngr->engine_cache_data(); 2227 : 133993 : + cache= &cache_data->cache_log; 2228 : 133993 : + context= &cache_data->engine_binlog_info; 2229 : : + /* 2230 : : + If we are binlogging from both stmt and trx cache in the same event 2231 : : + group, pass the engine context for out-of-band stmt data as 2232 : : + engine_ptr2. In this case, we have flushed everything in both 2233 : : + caches out as out-of-band data already. 2234 : : + */ 2235 : 267986 : + if (likely(cache_data->trx_cache()) && 2236 : 133993 : + unlikely(!cache_mngr->stmt_cache.empty())) 2237 : 10 : + context->engine_ptr2= 2238 : 10 : + cache_mngr->stmt_cache.engine_binlog_info.engine_ptr; 2239 : : + else 2240 : 133983 : + context->engine_ptr2= nullptr; 2241 : 133993 : + binlog_setup_engine_commit_data(context, cache_mngr); 2242 : 133993 : + gtid= thd->get_last_commit_gtid(); 2243 : : + } 2244 : 390981 : + *out_cache= cache; 2245 : 390981 : + *out_context= context; 2246 : 390981 : + *out_gtid= gtid; 2247 : 390981 : +} 2248 : : + 2249 : : + 2250 : : /** 2251 : : This function flushes the stmt-cache upon commit. 2252 : : 2314 : 11684 : DBUG_ASSERT(thd->transaction->xid_state.get_state_code() == 2315 : : XA_PREPARED); 2316 : : 2317 : 11684 : + if (opt_binlog_engine_hton) 2318 : : + { 2319 : 28 : + cache_mngr->trx_cache.engine_binlog_info.xa_xid= 2320 : 28 : + thd->transaction->xid_state.get_xid(); 2321 : 28 : + cache_mngr->trx_cache.engine_binlog_info.internal_xa= false; 2322 : : + } 2323 : : + else 2324 : : + { 2325 : 11656 : + buflen= serialize_with_xid(thd->transaction->xid_state.get_xid(), 2326 : : + buf, query, q_len); 2327 : : + } 2328 : : } 2329 : 104091 : Query_log_event end_evt(thd, buf, buflen, TRUE, TRUE, TRUE, 0); 2330 : : 2332 : 104084 : } 2333 : : 2334 : : 2335 : : +static int 2336 : 24 : +binlog_engine_xa_rollback(THD *thd, binlog_cache_mngr *cache_mngr) 2337 : : +{ 2338 : 24 : + DBUG_ASSERT(opt_binlog_engine_hton); 2339 : 24 : + DBUG_ASSERT(thd->transaction->xid_state.is_explicit_XA()); 2340 : : + 2341 : 24 : + int err= 0; 2342 : 24 : + binlog_cache_data *cache_data= cache_mngr->get_binlog_cache_data(true); 2343 : 24 : + handler_binlog_event_group_info *engine_context= 2344 : : + &cache_data->engine_binlog_info; 2345 : 24 : + const XID *xid= thd->transaction->xid_state.get_xid(); 2346 : 24 : + engine_context->xa_xid= xid; 2347 : 24 : + engine_context->internal_xa= false; 2348 : 24 : + mysql_mutex_lock(&LOCK_commit_ordered); 2349 : 48 : + err= (*opt_binlog_engine_hton->binlog_xa_rollback_ordered) 2350 : 24 : + (thd, xid, &engine_context->engine_ptr); 2351 : 24 : + mysql_mutex_unlock(&LOCK_commit_ordered); 2352 : 24 : + if (likely(!err)) 2353 : 48 : + err= (*opt_binlog_engine_hton->binlog_xa_rollback) 2354 : 24 : + (thd, xid, &engine_context->engine_ptr); 2355 : 24 : + cache_mngr->reset(false, true); 2356 : 24 : + cache_mngr->need_engine_2pc= true; 2357 : : + 2358 : 24 : + return err; 2359 : : +} 2360 : : + 2361 : : + 2362 : : /** 2363 : : This function flushes the trx-cache upon rollback. 2364 : : 2368 : : @return 2369 : : nonzero if an error pops up when flushing the cache. 2370 : : */ 2371 : : +static int 2372 : 17695 : binlog_rollback_flush_trx_cache(THD *thd, bool all, 2373 : : binlog_cache_mngr *cache_mngr) 2374 : : { 2377 : 17695 : char buf[q_len + ser_buf_size]= "ROLLBACK"; 2378 : 17695 : size_t buflen= sizeof("ROLLBACK") - 1; 2379 : : 2380 : 17695 : + if (unlikely(thd->transaction->xid_state.is_explicit_XA())) 2381 : : { 2382 : : /* for not prepared use plain ROLLBACK */ 2383 : 16412 : if (thd->transaction->xid_state.get_state_code() == XA_PREPARED) 2384 : : + { 2385 : 16368 : + if (opt_binlog_engine_hton) 2386 : : + { 2387 : 18 : + if (unlikely(!cache_mngr)) 2388 : 0 : + return 1; 2389 : 18 : + return binlog_engine_xa_rollback(thd, cache_mngr); 2390 : : + } 2391 : : + 2392 : 16350 : buflen= serialize_with_xid(thd->transaction->xid_state.get_xid(), 2393 : : buf, query, q_len); 2394 : : + } 2395 : : } 2396 : 17677 : Query_log_event end_evt(thd, buf, buflen, TRUE, TRUE, TRUE, 0); 2397 : : 2477 : : If rolling back a statement in a transaction, we truncate the 2478 : : transaction cache to remove the statement. 2479 : : */ 2480 : 1365 : + else if (!opt_binlog_engine_hton) 2481 : 1177 : trx_cache->restore_prev_position(); 2482 : : + else 2483 : : + { 2484 : 188 : + IO_CACHE *cache= &trx_cache->cache_log; 2485 : 188 : + my_off_t stmt_pos= trx_cache->get_prev_position(); 2486 : : + /* Drop any pending savepoints in the cache beyond statement start. */ 2487 : 188 : + binlog_savepoint_info **sp_ptr= &cache_mngr->cache_savepoint_list; 2488 : : + for (;;) 2489 : : + { 2490 : 188 : + binlog_savepoint_info *sp= *sp_ptr; 2491 : 188 : + if (!sp || sp->cache_offset > stmt_pos) 2492 : : + break; 2493 : 0 : + sp_ptr= &sp->next; 2494 : 0 : + } 2495 : 188 : + *sp_ptr= nullptr; 2496 : 188 : + cache_mngr->cache_savepoint_next_ptr= sp_ptr; 2497 : 188 : + if (stmt_pos >= cache->pos_in_file) 2498 : : + { 2499 : 181 : + trx_cache->restore_prev_position(); 2500 : 181 : + cache->write_function= binlog_spill_to_engine; 2501 : : + } 2502 : : + else 2503 : : + { 2504 : 7 : + trx_cache->set_prev_position(cache->pos_in_file); 2505 : 7 : + trx_cache->restore_prev_position(); 2506 : 7 : + trx_cache->reset_cache_for_engine(stmt_pos, binlog_spill_to_engine); 2507 : 7 : + trx_cache->engine_binlog_info.out_of_band_offset= stmt_pos; 2508 : 7 : + (*opt_binlog_engine_hton->binlog_savepoint_rollback) 2509 : 7 : + (thd, &trx_cache->engine_binlog_info.engine_ptr, 2510 : : + &cache_mngr->stmt_start_engine_ptr, nullptr); 2511 : : + } 2512 : 188 : + DBUG_ASSERT(cache->write_function == binlog_spill_to_engine); 2513 : : + } 2514 : : 2515 : 6931 : DBUG_ASSERT(trx_cache->pending() == NULL); 2516 : 6931 : DBUG_RETURN(error); 2525 : : } 2526 : : 2527 : : 2528 : : +int 2529 : 32367 : +MYSQL_BIN_LOG::log_xa_prepare(THD *thd, bool all) 2530 : : { 2531 : : /* Do nothing unless the transaction is a user XA. */ 2532 : 64734 : + if (is_preparing_xa(thd) && 2533 : 32367 : + thd->ha_data[binlog_tp.slot].ha_info[1].is_started()) 2534 : : + { 2535 : 28590 : + if (opt_binlog_engine_hton) 2536 : : + { 2537 : : + /* 2538 : : + Tell the binlog engine to persist the event data for the current 2539 : : + transaction, identified by the user-supplied XID. 2540 : : + This way, a later XA COMMIT can then be binlogged correctly with the 2541 : : + persisted event data, even across server restart. 2542 : : + */ 2543 : 52 : + binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data(); 2544 : 52 : + if (unlikely(!cache_mngr)) 2545 : 0 : + return 1; 2546 : 52 : + binlog_cache_data *cache_data= cache_mngr->get_binlog_cache_data(true); 2547 : : + /* Put in the end event. */ 2548 : : + { 2549 : : + Query_log_event end_ev(thd, STRING_WITH_LEN("COMMIT"), 2550 : 52 : + TRUE, TRUE, TRUE, 0); 2551 : 52 : + end_ev.cache_type= Log_event::EVENT_TRANSACTIONAL_CACHE; 2552 : 52 : + if (write_event(&end_ev, BINLOG_CHECKSUM_ALG_OFF, 0, 2553 : : + &cache_data->cache_log)) 2554 : 0 : + return 1; 2555 : 52 : + } 2556 : : + /* Make sure all event data is flushed as OOB. */ 2557 : 52 : + if (unlikely(my_b_flush_io_cache(&cache_data->cache_log, 0))) 2558 : 0 : + return 1; 2559 : 52 : + handler_binlog_event_group_info *engine_context= 2560 : : + &cache_data->engine_binlog_info; 2561 : 52 : + engine_context->xa_xid= thd->transaction->xid_state.get_xid(); 2562 : 52 : + uchar engine_count= (uchar)ha_count_rw_2pc(thd, true); 2563 : 52 : + mysql_mutex_lock(&LOCK_commit_ordered); 2564 : 52 : + bool err= (*opt_binlog_engine_hton->binlog_write_xa_prepare_ordered) 2565 : 52 : + (thd, engine_context, engine_count); 2566 : 52 : + mysql_mutex_unlock(&LOCK_commit_ordered); 2567 : 52 : + if (likely(!err)) 2568 : 52 : + err= (*opt_binlog_engine_hton->binlog_write_xa_prepare) 2569 : 52 : + (thd, engine_context, engine_count); 2570 : 52 : + cache_mngr->reset(false, true); 2571 : 52 : + return err; 2572 : : + } 2573 : : + else 2574 : 28538 : + return binlog_commit(thd, all, FALSE); 2575 : : + } 2576 : 3777 : + return 0; 2577 : : +} 2578 : : + 2579 : : + 2580 : 1246541 : +static int binlog_prepare(THD *thd, bool all) 2581 : : +{ 2582 : : + /* 2583 : : + ToDo: We do not really need a prepare() hton method in the binlog, we are 2584 : : + the transaction coordinator, should do our work in log_xa_prepare(). 2585 : : + 2586 : : + There is currently code that looks at registered htons if they have the 2587 : : + "prepare" method and use that to decide how the transaction should be 2588 : : + handled; until this is refactored, we need to have a prepare method in the 2589 : : + binlog which just does nothing. 2590 : : + */ 2591 : 1246541 : + return 0; 2592 : : } 2593 : : 2594 : : 2800 : 11718 : !(thd->ha_data[binlog_tp.slot].ha_info[1].is_started() && 2801 : 11713 : thd->ha_data[binlog_tp.slot].ha_info[1].is_trx_read_write()))) 2802 : : { 2803 : 525031 : + if (unlikely(thd->transaction->xid_state.get_state_code() == XA_PREPARED) && 2804 : 34 : + opt_binlog_engine_hton) 2805 : : + { 2806 : : + /* 2807 : : + The XA transaction is empty, so we just need to inform the binlog 2808 : : + engine that it is complete, there is no actual transaction to binlog. 2809 : : + Thus, we can just treat this as a rollback. 2810 : : + */ 2811 : 6 : + error= binlog_engine_xa_rollback(thd, cache_mngr); 2812 : : + } 2813 : : + 2814 : : /* 2815 : : This is an empty transaction commit (both the regular and xa), 2816 : : or such transaction xa-prepare or 2840 : 132462 : if (cache_mngr->need_unlog && !is_xa_prepare) 2841 : : { 2842 : : error= 2843 : 0 : + mysql_bin_log.unlog(thd, 2844 : 0 : + BINLOG_COOKIE_MAKE(cache_mngr->binlog_id, 2845 : : cache_mngr->delayed_error), 1); 2846 : 0 : cache_mngr->need_unlog= false; 2847 : : } 2861 : 1071535 : DBUG_RETURN(error); 2862 : 1596538 : } 2863 : : 2864 : : + 2865 : : +void 2866 : 608974 : +binlog_post_commit(THD *thd, bool all) 2867 : : +{ 2868 : 608974 : + if (!opt_binlog_engine_hton) 2869 : 474514 : + return; 2870 : : + 2871 : 134460 : + binlog_cache_mngr *cache_mngr= thd->binlog_get_cache_mngr(); 2872 : 134462 : + if (likely(cache_mngr != nullptr) && unlikely(cache_mngr->need_engine_2pc)) 2873 : : + { 2874 : 16 : + DBUG_ASSERT(!cache_mngr->trx_cache.engine_binlog_info.internal_xa); 2875 : 16 : + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_COMMIT && 2876 : : + thd->lex->xa_opt != XA_ONE_PHASE); 2877 : 16 : + cache_mngr->need_engine_2pc= false; 2878 : 16 : + (*opt_binlog_engine_hton->binlog_unlog) 2879 : 16 : + (thd->transaction->xid_state.get_xid(), 2880 : : + &cache_mngr->trx_cache.engine_binlog_info.engine_ptr); 2881 : : + } 2882 : : +} 2883 : : + 2884 : : + 2885 : : +void 2886 : 6897 : +binlog_post_commit_by_xid(XID *xid) 2887 : : +{ 2888 : 6897 : + if (!opt_binlog_engine_hton) 2889 : 6879 : + return; 2890 : : + 2891 : 18 : + THD *thd= current_thd; 2892 : 18 : + binlog_cache_mngr *cache_mngr= thd->binlog_get_cache_mngr(); 2893 : 18 : + if (likely(cache_mngr != nullptr) && unlikely(cache_mngr->need_engine_2pc)) 2894 : : + { 2895 : 18 : + DBUG_ASSERT(!cache_mngr->trx_cache.engine_binlog_info.internal_xa); 2896 : 18 : + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_COMMIT && 2897 : : + thd->lex->xa_opt != XA_ONE_PHASE); 2898 : 18 : + cache_mngr->need_engine_2pc= false; 2899 : 18 : + (*opt_binlog_engine_hton->binlog_unlog) 2900 : 18 : + (xid, &cache_mngr->trx_cache.engine_binlog_info.engine_ptr); 2901 : : + } 2902 : : +} 2903 : : + 2904 : : + 2905 : : +void 2906 : 21555 : +binlog_post_rollback(THD *thd, bool all) 2907 : : +{ 2908 : 21555 : + if (!opt_binlog_engine_hton) 2909 : 18010 : + return; 2910 : 3545 : + binlog_cache_mngr *cache_mngr= thd->binlog_get_cache_mngr(); 2911 : 3545 : + if (likely(cache_mngr != nullptr) && unlikely(cache_mngr->need_engine_2pc)) 2912 : : + { 2913 : 8 : + handler_binlog_event_group_info *context= 2914 : : + &cache_mngr->trx_cache.engine_binlog_info; 2915 : 8 : + DBUG_ASSERT(!context->internal_xa); 2916 : 8 : + if (!context->internal_xa) 2917 : : + { 2918 : 8 : + const XID *xid= thd->transaction->xid_state.get_xid(); 2919 : 8 : + (*opt_binlog_engine_hton->binlog_unlog)(xid, &context->engine_ptr); 2920 : : + } 2921 : 8 : + cache_mngr->need_engine_2pc= false; 2922 : : + } 2923 : : +} 2924 : : + 2925 : : + 2926 : : +void 2927 : 9290 : +binlog_post_rollback_by_xid(XID *xid) 2928 : : +{ 2929 : 9290 : + if (!opt_binlog_engine_hton) 2930 : 9280 : + return; 2931 : : + 2932 : 10 : + THD *thd= current_thd; 2933 : 10 : + binlog_cache_mngr *cache_mngr= thd->binlog_get_cache_mngr(); 2934 : 10 : + if (likely(cache_mngr != nullptr) && unlikely(cache_mngr->need_engine_2pc)) 2935 : : + { 2936 : 10 : + DBUG_ASSERT(!cache_mngr->trx_cache.engine_binlog_info.internal_xa); 2937 : 10 : + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_XA_ROLLBACK); 2938 : 10 : + cache_mngr->need_engine_2pc= false; 2939 : 10 : + (*opt_binlog_engine_hton->binlog_unlog) 2940 : 10 : + (xid, &cache_mngr->trx_cache.engine_binlog_info.engine_ptr); 2941 : : + } 2942 : : +} 2943 : : + 2944 : : + 2945 : : /** 2946 : : This function is called when a transaction or a statement is rolled back. 2947 : : 2992 : 16454 : !(thd->ha_data[binlog_tp.slot].ha_info[1].is_started() && 2993 : 16454 : thd->ha_data[binlog_tp.slot].ha_info[1].is_trx_read_write()))) 2994 : : { 2995 : 4574 : + if (unlikely(thd->transaction->xid_state.get_state_code() == XA_PREPARED) && 2996 : 86 : + opt_binlog_engine_hton) 2997 : : + { 2998 : : + /* 2999 : : + The XA transaction is empty, so we just need to inform the binlog 3000 : : + engine that it is complete, there is no actual transaction to binlog. 3001 : : + Thus, we can just treat this as a rollback. 3002 : : + */ 3003 : 0 : + error= binlog_engine_xa_rollback(thd, cache_mngr); 3004 : : + } 3005 : : + 3006 : : /* 3007 : : The same comments apply as in the binlog commit method's branch. 3008 : : */ 3221 : : or "RELEASE S" without the preceding "SAVEPOINT S" in the binary 3222 : : log. 3223 : : */ 3224 : 1625 : + if (unlikely((error= mysql_bin_log.write(&qinfo)) != 0)) 3225 : 0 : + DBUG_RETURN(error); 3226 : 1625 : + binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data(); 3227 : 1625 : + binlog_savepoint_info *sp_info= (binlog_savepoint_info*)sv; 3228 : 1625 : + binlog_trans_log_savepos(thd, cache_mngr, &sp_info->cache_offset); 3229 : 1625 : + if (opt_binlog_engine_hton) 3230 : : + { 3231 : : + /* 3232 : : + Add the savepoint to the list of pending savepoints in the trx cache. 3233 : : + If the savepoint gets spilled to the binlog as oob data, then we need 3234 : : + to create an (engine) binlog savepoint from it so that the engine can 3235 : : + roll back the oob data if needed. 3236 : : + As long as the savepoint is in the cache, we can simply roll it back 3237 : : + by truncating the cache. 3238 : : + 3239 : : + Note that re-using the savepoint name is legal in SQL: 3240 : : + 3241 : : + BEGIN 3242 : : + SAVEPOINT A; 3243 : : + ... 3244 : : + SAVEPOINT A; 3245 : : + ... 3246 : : + ROLLBACK TO A; 3247 : : + 3248 : : + In this case, the second instance replaces the first one, and we get 3249 : : + called with the same sv pointer again. So we traverse the list 3250 : : + and remove the old instance, if found, before adding the new one. 3251 : : + */ 3252 : 1212 : + binlog_savepoint_info *sp= cache_mngr->cache_savepoint_list; 3253 : 1212 : + binlog_savepoint_info **next_ptr= &cache_mngr->cache_savepoint_list; 3254 : 1554 : + while (sp) 3255 : : + { 3256 : 342 : + if (sp == sp_info) 3257 : : + { 3258 : : + /* 3259 : : + The upper layer (in handler.cc) removes the savepoint and calls 3260 : : + binlog_savepoint_release() for us, so we do not expect to have to 3261 : : + remove anything here. But let's still do so as defensive coding, 3262 : : + but assert that it won't be necessary. 3263 : : + */ 3264 : 0 : + DBUG_ASSERT("Should be removed by ha_release_savepoint()" == nullptr); 3265 : 0 : + *next_ptr= sp->next; 3266 : : + } 3267 : : + else 3268 : 342 : + next_ptr= &sp->next; 3269 : 342 : + sp=sp->next; 3270 : : + } 3271 : : + 3272 : : + /* 3273 : : + Assert that the existing cache_savepoint_next_ptr matches either 3274 : : + the end of the list now, or it was pointing to the savepoint that 3275 : : + is now being set, which is being reused and was deleted from the 3276 : : + (end of the) list. 3277 : : + */ 3278 : 1212 : + DBUG_ASSERT(next_ptr == cache_mngr->cache_savepoint_next_ptr || 3279 : : + cache_mngr->cache_savepoint_next_ptr == &sp_info->next); 3280 : : + /* Insert the savepoint at the end of the list. */ 3281 : 1212 : + *next_ptr= sp_info; 3282 : 1212 : + cache_mngr->cache_savepoint_next_ptr= &sp_info->next; 3283 : 1212 : + sp_info->next= nullptr; 3284 : 1212 : + sp_info->engine_ptr= nullptr; 3285 : : + } 3286 : : 3287 : 1625 : DBUG_RETURN(error); 3288 : 1625 : } 3289 : : 3290 : : + 3291 : : +/* 3292 : : + Release a savepoint. 3293 : : + We only need to release if the savepoint is still pending in the cache. 3294 : : + If the savepoint has been spilled to the engine, it has already been 3295 : : + removed from the list, and the engine will just ignore it. 3296 : : +*/ 3297 : : +static int 3298 : 88 : +binlog_savepoint_release(THD *thd, void *sv) 3299 : : +{ 3300 : 88 : + if (!opt_binlog_engine_hton) 3301 : 68 : + return 0; 3302 : : + 3303 : 20 : + binlog_savepoint_info *sp_info= (binlog_savepoint_info*)sv; 3304 : 20 : + binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data(); 3305 : 20 : + binlog_savepoint_info *sp= cache_mngr->cache_savepoint_list; 3306 : 20 : + binlog_savepoint_info **next_ptr= &cache_mngr->cache_savepoint_list; 3307 : 42 : + while (sp) 3308 : : + { 3309 : 22 : + if (sp == sp_info) 3310 : 6 : + *next_ptr= sp->next; 3311 : : + else 3312 : 16 : + next_ptr= &sp->next; 3313 : 22 : + sp=sp->next; 3314 : : + } 3315 : : + /* Make sure to update cache_savepoint_next_ptr if we delete last in list. */ 3316 : 20 : + cache_mngr->cache_savepoint_next_ptr= next_ptr; 3317 : 20 : + return 0; 3318 : : +} 3319 : : + 3320 : : + 3321 : 1318 : static int binlog_savepoint_rollback(THD *thd, void *sv) 3322 : : { 3323 : 1318 : DBUG_ENTER("binlog_savepoint_rollback"); 3345 : 109 : DBUG_RETURN(mysql_bin_log.write(&qinfo)); 3346 : 109 : } 3347 : : 3348 : 1209 : + binlog_trans_log_truncate(thd, (binlog_savepoint_info *)sv); 3349 : : 3350 : : /* 3351 : : When a SAVEPOINT is executed inside a stored function/trigger we force the 4357 : : 4358 : 140095 : inited= 0; 4359 : 140095 : mysql_mutex_lock(&LOCK_log); 4360 : 140096 : + if (opt_binlog_engine_hton) 4361 : : + { 4362 : 424 : + if (!is_relay_log) 4363 : 161 : + close_engine(); 4364 : : + } 4365 : : + else 4366 : 139672 : + close(LOG_CLOSE_INDEX|LOG_CLOSE_STOP_EVENT); 4367 : 140092 : mysql_mutex_unlock(&LOCK_log); 4368 : 140096 : delete description_event_for_queue; 4369 : 140096 : delete description_event_for_exec; 4383 : : 4384 : 140095 : mysql_mutex_destroy(&LOCK_log); 4385 : 140096 : mysql_mutex_destroy(&LOCK_index); 4386 : 140096 : + mysql_mutex_destroy(&LOCK_binlog_use); 4387 : 140096 : mysql_mutex_destroy(&LOCK_xid_list); 4388 : 140095 : mysql_mutex_destroy(&LOCK_binlog_background_thread); 4389 : 140095 : mysql_mutex_destroy(&LOCK_binlog_end_pos); 4390 : 140094 : mysql_cond_destroy(&COND_relay_log_updated); 4391 : 140096 : mysql_cond_destroy(&COND_bin_log_updated); 4392 : 140096 : mysql_cond_destroy(&COND_queue_busy); 4393 : 140096 : + mysql_cond_destroy(&COND_binlog_use); 4394 : 140096 : mysql_cond_destroy(&COND_xid_list); 4395 : 140096 : mysql_cond_destroy(&COND_binlog_background_thread); 4396 : 140096 : mysql_cond_destroy(&COND_binlog_background_thread_end); 4412 : 146762 : Event_log::init_pthread_objects(); 4413 : 146763 : mysql_mutex_init(m_key_LOCK_index, &LOCK_index, MY_MUTEX_INIT_SLOW); 4414 : 146763 : mysql_mutex_setflags(&LOCK_index, MYF_NO_DEADLOCK_DETECTION); 4415 : 146763 : + mysql_mutex_init(key_BINLOG_LOCK_binlog_use, &LOCK_binlog_use, 4416 : : + MY_MUTEX_INIT_SLOW); 4417 : 146761 : mysql_mutex_init(key_BINLOG_LOCK_xid_list, 4418 : : &LOCK_xid_list, MY_MUTEX_INIT_FAST); 4419 : 146758 : mysql_cond_init(m_key_relay_log_update, &COND_relay_log_updated, 0); 4420 : 146760 : mysql_cond_init(m_key_bin_log_update, &COND_bin_log_updated, 0); 4421 : 146761 : mysql_cond_init(m_key_COND_queue_busy, &COND_queue_busy, 0); 4422 : 146762 : + mysql_cond_init(key_BINLOG_COND_binlog_use, &COND_binlog_use, 0); 4423 : 146761 : mysql_cond_init(key_BINLOG_COND_xid_list, &COND_xid_list, 0); 4424 : : 4425 : 146762 : mysql_mutex_init(key_BINLOG_LOCK_binlog_background_thread, 4591 : 79319 : xid_count_per_binlog *new_xid_list_entry= NULL, *b; 4592 : 79319 : DBUG_ENTER("MYSQL_BIN_LOG::open"); 4593 : : 4594 : 79319 : + DBUG_ASSERT(is_relay_log || !opt_binlog_engine_hton); 4595 : 79319 : mysql_mutex_assert_owner(&LOCK_log); 4596 : : 4597 : 79319 : if (!is_relay_log) 4980 : 79287 : } 4981 : : 4982 : : 4983 : : +/* 4984 : : + Open the binlog implemented in a storage engine (--binlog-storage-engine). */ 4985 : : +bool 4986 : 169 : +MYSQL_BIN_LOG::open_engine(handlerton *hton, ulong max_size, const char *dir) 4987 : : +{ 4988 : 169 : + binlog_max_spill_size= std::min((size_t)(max_size / 2), BINLOG_SPILL_MAX); 4989 : : + 4990 : 169 : + log_state= LOG_OPENED; 4991 : : + { 4992 : : + /* 4993 : : + Write a format description event to the binlog at server restart. 4994 : : + With --binlog-storage-engine, we do not write a format description event 4995 : : + at the start of every binlog file (indeed, the "start of binlog file" is 4996 : : + mostly a meaningless concept). But we want to inform the slaves about 4997 : : + master server restarts, and sending a format description event (with the 4998 : : + `created' flag set) is a backwards-compatible way of doing so. 4999 : : + */ 5000 : : + Format_description_log_event s(BINLOG_VERSION, NULL, 5001 : 169 : + BINLOG_CHECKSUM_ALG_OFF); 5002 : 169 : + s.dont_set_created= false; 5003 : : + /* Set stmt cache so end_log_pos gets written as 0. */ 5004 : 169 : + s.cache_type= Log_event::EVENT_STMT_CACHE; 5005 : : + 5006 : : + IO_CACHE cache; 5007 : 169 : + init_io_cache(&cache, (File)-1, binlog_cache_size, WRITE_CACHE, 0, false, 5008 : : + MYF(MY_DONT_CHECK_FILESIZE)); 5009 : 169 : + handler_binlog_event_group_info engine_context= 5010 : : + { 0, 0, nullptr, nullptr, nullptr, 0, 0, 0 }; 5011 : 169 : + write_event(&s, BINLOG_CHECKSUM_ALG_OFF, 0, &cache); 5012 : 169 : + mysql_mutex_lock(&LOCK_commit_ordered); 5013 : 169 : + (*opt_binlog_engine_hton->binlog_write_direct_ordered) (&cache, 5014 : : + &engine_context, 5015 : : + nullptr); 5016 : 169 : + mysql_mutex_unlock(&LOCK_commit_ordered); 5017 : 169 : + (*opt_binlog_engine_hton->binlog_write_direct) (&cache, 5018 : : + &engine_context, 5019 : : + nullptr); 5020 : 169 : + (*opt_binlog_engine_hton->binlog_oob_free)(engine_context.engine_ptr); 5021 : 169 : + end_io_cache(&cache); 5022 : 169 : + } 5023 : : + 5024 : 169 : + return false; 5025 : : +} 5026 : : + 5027 : : + 5028 : 75017 : int MYSQL_BIN_LOG::get_current_log(LOG_INFO* linfo) 5029 : : { 5030 : 75017 : mysql_mutex_lock(&LOCK_log); 5256 : 422414 : length= strlen(full_fname); 5257 : : } 5258 : : 5259 : 422414 : + full_fname[length-1]= 0; // kill \n 5260 : 422414 : + linfo->index_file_offset= my_b_tell(&index_file); 5261 : : + 5262 : 502796 : +err: 5263 : 502796 : + if (need_lock) 5264 : 416850 : + mysql_mutex_unlock(&LOCK_index); 5265 : 502796 : + return error; 5266 : : +} 5267 : : + 5268 : : + 5269 : : +/* 5270 : : + Start reading the binlog, eg. a slave dump thread. 5271 : : + Wait for any already running RESET MASTER to complete. 5272 : : + Then increment the binlog use count. 5273 : : + Must be paired with a call to end_use_binlog() when use of the binlog is 5274 : : + complete by the reader, unless start_use_binlog() returns true/error. 5275 : : + 5276 : : + Returns: 5277 : : + false Successfully marked binlog in use. 5278 : : + true Error (wait was terminated by kill). 5279 : : +*/ 5280 : : +bool 5281 : 142045 : +MYSQL_BIN_LOG::start_use_binlog(THD *thd) 5282 : : +{ 5283 : : + PSI_stage_info old_stage; 5284 : 142045 : + bool killed_err= false; 5285 : : + 5286 : 142045 : + if (unlikely(is_relay_log)) 5287 : : + { 5288 : 0 : + DBUG_ASSERT(FALSE); 5289 : 0 : + return false; 5290 : : + } 5291 : : + 5292 : 142045 : + mysql_mutex_lock(&LOCK_binlog_use); 5293 : 142045 : + thd->ENTER_COND(&COND_binlog_use, &LOCK_binlog_use, 5294 : : + &stage_waiting_for_reset_master, &old_stage); 5295 : 142045 : + while (binlog_use_count < 0 && !thd->check_killed(1)) 5296 : 0 : + mysql_cond_wait(&COND_binlog_use, &LOCK_binlog_use); 5297 : 142045 : + if (binlog_use_count < 0) 5298 : 0 : + killed_err= true; 5299 : : + else 5300 : 142045 : + ++binlog_use_count; 5301 : 142045 : + thd->EXIT_COND(&old_stage); 5302 : : + 5303 : 142045 : + return killed_err; 5304 : : +} 5305 : : + 5306 : : + 5307 : : +/* 5308 : : + Stop reading the binlog, eg. a slave dump thread. 5309 : : + Must be called after a successful start_use_binlog(), once the use of the 5310 : : + binlog has completed. 5311 : : +*/ 5312 : : +void 5313 : 141984 : +MYSQL_BIN_LOG::end_use_binlog(THD *thd) 5314 : : +{ 5315 : 141984 : + if (unlikely(is_relay_log)) 5316 : : + { 5317 : 0 : + DBUG_ASSERT(FALSE); 5318 : 0 : + return; 5319 : : + } 5320 : : + 5321 : 141984 : + mysql_mutex_lock(&LOCK_binlog_use); 5322 : 141987 : + if (likely(binlog_use_count > 0)) 5323 : 141987 : + --binlog_use_count; 5324 : : + else 5325 : 0 : + DBUG_ASSERT(FALSE); 5326 : 141987 : + mysql_mutex_unlock(&LOCK_binlog_use); 5327 : : } 5328 : : 5329 : : 5364 : 0 : DBUG_RETURN(1); 5365 : : } 5366 : : 5367 : : + /* 5368 : : + Give an error if any slave dump threads are running, and prevent any 5369 : : + new binlog readers (or another RESET MASTER) from running concurrently. 5370 : : + */ 5371 : 13601 : + mysql_mutex_lock(&LOCK_binlog_use); 5372 : 13601 : + if (binlog_use_count) 5373 : : + { 5374 : 5 : + my_error(ER_BINLOG_IN_USE, MYF(0)); 5375 : 5 : + mysql_mutex_unlock(&LOCK_binlog_use); 5376 : 5 : + DBUG_RETURN(1); 5377 : : + } 5378 : 13596 : + binlog_use_count= -1; 5379 : 13596 : + mysql_mutex_unlock(&LOCK_binlog_use); 5380 : : + 5381 : 13596 : + if (opt_binlog_engine_hton) 5382 : : + { 5383 : 1718 : + if (next_log_number) 5384 : : + { 5385 : 0 : + my_error(ER_NOT_AVAILABLE_WITH_ENGINE_BINLOG, MYF(0), 5386 : : + "RESET MASTER TO"); 5387 : 0 : + error= true; 5388 : : + } 5389 : : + else 5390 : : + { 5391 : 1718 : + DBUG_ASSERT(create_new_log); 5392 : 1718 : + error= reset_engine_binlogs(thd, init_state, init_state_len); 5393 : : + } 5394 : 1718 : + goto exit_engine_binlog; 5395 : : + } 5396 : : + 5397 : : /* 5398 : : Mark that a RESET MASTER is in progress. 5399 : : This ensures that a binlog checkpoint will not try to write binlog 5652 : : 5653 : 42805 : mysql_mutex_unlock(&LOCK_index); 5654 : 42805 : mysql_mutex_unlock(&LOCK_log); 5655 : : + 5656 : 42805 : + if (!is_relay_log) 5657 : : + { 5658 : 11878 : +exit_engine_binlog: 5659 : 13596 : + mysql_mutex_lock(&LOCK_binlog_use); 5660 : 13596 : + DBUG_ASSERT(binlog_use_count == -1); 5661 : 13596 : + binlog_use_count= 0; 5662 : 13596 : + mysql_cond_signal(&COND_binlog_use); 5663 : 13596 : + mysql_mutex_unlock(&LOCK_binlog_use); 5664 : : + } 5665 : : + 5666 : 44523 : DBUG_RETURN(error); 5667 : 44528 : } 5668 : : 5669 : : 5670 : : +bool 5671 : 1718 : +MYSQL_BIN_LOG::reset_engine_binlogs(THD *thd, rpl_gtid *init_state, 5672 : : + uint32 init_state_len) 5673 : : +{ 5674 : : + bool err; 5675 : 1718 : + DBUG_ASSERT(!is_relay_log); 5676 : : + 5677 : 1718 : + mysql_mutex_lock(&LOCK_log); 5678 : 1718 : + mysql_mutex_lock(&LOCK_index); 5679 : 1718 : + mysql_mutex_lock(&LOCK_after_binlog_sync); 5680 : 1718 : + mysql_mutex_unlock(&LOCK_log); 5681 : 1718 : + mysql_mutex_lock(&LOCK_commit_ordered); 5682 : 1718 : + mysql_mutex_unlock(&LOCK_after_binlog_sync); 5683 : : + 5684 : 1718 : + if (init_state) 5685 : 0 : + rpl_global_gtid_binlog_state.load(init_state, init_state_len); 5686 : : + else 5687 : 1718 : + rpl_global_gtid_binlog_state.reset(); 5688 : 1718 : + err= (*opt_binlog_engine_hton->reset_binlogs)(); 5689 : : + 5690 : 1718 : + mysql_mutex_unlock(&LOCK_commit_ordered); 5691 : 1718 : + mysql_mutex_unlock(&LOCK_index); 5692 : : + 5693 : 1718 : + return err; 5694 : : +} 5695 : : + 5696 : : + 5697 : 105 : void MYSQL_BIN_LOG::wait_for_last_checkpoint_event() 5698 : : { 5699 : 105 : mysql_mutex_lock(&LOCK_xid_list); 6188 : : } 6189 : : else 6190 : : { 6191 : 6248 : + if (likely((error= find_log_pos(&check_log_info, 6192 : : + log_info.log_file_name, need_mutex)))) 6193 : : { 6194 : 6240 : if (error != LOG_INFO_EOF) 6195 : : { 6462 : 84 : DBUG_RETURN(error); 6463 : 84 : } 6464 : : 6465 : : + 6466 : : +void 6467 : 6 : +MYSQL_BIN_LOG::engine_purge_logs_by_size(ulonglong max_total_size) 6468 : : +{ 6469 : 6 : + DBUG_ASSERT(opt_binlog_engine_hton); 6470 : 6 : + if (!is_open()) 6471 : 0 : + return; 6472 : : + 6473 : : + handler_binlog_purge_info purge_info; 6474 : 6 : + auto p= engine_binlog_in_use(); 6475 : 6 : + purge_info.limit_file_no= p.first; 6476 : 6 : + uint num_dump_threads= p.second; 6477 : 6 : + if (num_dump_threads < slave_connections_needed_for_purge) 6478 : : + { 6479 : 0 : + purge_info.limit_file_no= 0; 6480 : 0 : + purge_info.nonpurge_reason= "less than " 6481 : : + "'slave_connections_needed_for_purge' slaves have processed it"; 6482 : : + } 6483 : : + else 6484 : 6 : + purge_info.nonpurge_reason= nullptr; 6485 : 6 : + purge_info.nonpurge_filename[0]= '\0'; 6486 : 6 : + purge_info.purge_by_date= false; 6487 : 6 : + purge_info.limit_date= my_time(0); 6488 : 6 : + purge_info.purge_by_size= true; 6489 : 6 : + purge_info.limit_size= max_total_size; 6490 : 6 : + purge_info.purge_by_name= false; 6491 : 6 : + purge_info.limit_name= nullptr; 6492 : 6 : + int res= (*opt_binlog_engine_hton->binlog_purge)(&purge_info); 6493 : 6 : + if (res && purge_info.nonpurge_reason) 6494 : 4 : + give_purge_note(purge_info.nonpurge_reason, 6495 : : + purge_info.nonpurge_filename, true); 6496 : : +} 6497 : : + 6498 : : + 6499 : : /* 6500 : : @param log_file_name_arg Name of log file to check 6501 : : @param interactive True if called by a PURGE BINLOG command. 6526 : : int res; 6527 : : const char *reason; 6528 : : 6529 : 6402 : + DBUG_ASSERT(is_relay_log || !opt_binlog_engine_hton); 6530 : 12774 : if (is_active(log_file_name_arg) || 6531 : 6372 : (!is_relay_log && waiting_for_slave_to_change_binlog && 6532 : 12 : purge_sending_new_binlog_file == sending_new_binlog_file && 6590 : : 6591 : : /* purge_warning_given is reset after next successful purge */ 6592 : 62 : purge_warning_given= 1; 6593 : 62 : + give_purge_note(reason, log_file_name_arg, interactive); 6594 : : + } 6595 : 64 : + return 0; 6596 : : +} 6597 : : +#endif /* HAVE_REPLICATION */ 6598 : : + 6599 : : + 6600 : : +void 6601 : 90 : +give_purge_note(const char *reason, const char *file_name, bool interactive) 6602 : : +{ 6603 : 90 : + if (interactive) 6604 : : + { 6605 : 44 : + if (file_name && file_name[0]) 6606 : 44 : my_printf_error(ER_BINLOG_PURGE_PROHIBITED, 6607 : : "Binary log '%s' is not purged because %s", 6608 : : + MYF(ME_NOTE), file_name, reason); 6609 : : else 6610 : 0 : + my_printf_error(ER_BINLOG_PURGE_PROHIBITED, 6611 : : + "Binary log purge is prevented because %s", 6612 : : + MYF(ME_NOTE), reason); 6613 : : + } 6614 : : + else 6615 : : + { 6616 : 46 : + if (file_name && file_name[0]) 6617 : 46 : sql_print_information("Binary log '%s' is not purged because %s", 6618 : : + file_name, reason); 6619 : : + else 6620 : 0 : + sql_print_information("Binary log purge is prevented because %s", 6621 : : + reason); 6622 : : } 6623 : 90 : } 6624 : : 6625 : : 6626 : : /** 6640 : 34 : LOG_INFO log_info; 6641 : 34 : DBUG_ENTER("count_binlog_space"); 6642 : : 6643 : 34 : + DBUG_ASSERT(!opt_binlog_engine_hton); 6644 : 34 : binlog_space_total = 0; 6645 : 34 : if ((error= find_log_pos(&log_info, NullS, false /*need_lock_index=false*/))) 6646 : 0 : goto done; 7103 : : 7104 : 2836311 : bool MYSQL_BIN_LOG::flush_and_sync(bool *synced) 7105 : : { 7106 : 2836311 : + DBUG_ASSERT(is_relay_log || !opt_binlog_engine_hton); 7107 : 2836311 : int err=0, fd=log_file.file; 7108 : 2836311 : if (synced) 7109 : 722642 : *synced= 0; 7272 : 1957 : return (thd->transaction->stmt.modified_non_trans_table); 7273 : : } 7274 : : 7275 : : + 7276 : : +static int 7277 : 7184 : +binlog_spill_to_engine(struct st_io_cache *cache, const uchar *data, size_t len) 7278 : : +{ 7279 : : + /* 7280 : : + Tricky: The mysys IO_CACHE write function can be called either from 7281 : : + my_b_flush_io_cache(), where it must write everything it was asked to; or 7282 : : + from _my_b_write(), where it needs only write as much as is efficient (eg. 7283 : : + an integer multiple of some block size), and any remainder (which must be 7284 : : + < cache size) will be put in the cache. 7285 : : + 7286 : : + The two cases are distinguished on whether the passed-in data pointer is 7287 : : + equal to cache->write_buffer or not. 7288 : : + 7289 : : + We want each oob record to be the full size, so write only integer 7290 : : + multiples of the cache size in the latter case. 7291 : : + */ 7292 : 7184 : + if (data != cache->write_buffer) 7293 : : + { 7294 : 3205 : + len-= (len % cache->buffer_length); 7295 : 3205 : + if (!len) 7296 : 2207 : + return 0; 7297 : : + } 7298 : : + 7299 : 4977 : + binlog_cache_mngr *mngr= (binlog_cache_mngr *)cache->append_read_pos; 7300 : : + binlog_cache_data *cache_data; 7301 : : + bool using_trx_cache; 7302 : 4977 : + if (unlikely(cache==&mngr->stmt_cache.cache_log)) 7303 : : + { 7304 : 100 : + cache_data= &mngr->stmt_cache; 7305 : 100 : + using_trx_cache= false; 7306 : : + } 7307 : : + else 7308 : : + { 7309 : 4877 : + cache_data= &mngr->trx_cache; 7310 : 4877 : + using_trx_cache= true; 7311 : : + } 7312 : 4977 : + void **engine_ptr= &cache_data->engine_binlog_info.engine_ptr; 7313 : 4977 : + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); 7314 : : + 7315 : 4977 : + size_t max_len= std::min(binlog_max_spill_size, (size_t)binlog_cache_size); 7316 : 4977 : + my_off_t spill_end= cache->pos_in_file + len; 7317 : 4977 : + size_t sofar= 0; 7318 : 4977 : + void **stmt_start_ptr= nullptr; 7319 : 4977 : + void **savepoint_ptr= nullptr; 7320 : : + 7321 : : + /* 7322 : : + If there are any pending savepoints (or a start-of-statement point) in the 7323 : : + cache data that we're now spilling to the engine binlog, set an engine 7324 : : + savepoint for each of them so that we can roll back such spilled data, 7325 : : + if required. 7326 : : + */ 7327 : 4977 : + if (data == cache->write_buffer && using_trx_cache) 7328 : : + { 7329 : 3883 : + my_off_t spill_start= cache->pos_in_file; 7330 : 3883 : + my_off_t stmt_pos= mngr->trx_cache.get_prev_position(); 7331 : 3315 : + bool do_stmt_pos= stmt_pos != MY_OFF_T_UNDEF && 7332 : 7198 : + stmt_pos >= spill_start && stmt_pos < spill_end; 7333 : 3883 : + binlog_savepoint_info *sp= mngr->cache_savepoint_list; 7334 : : + for (;;) 7335 : : + { 7336 : : + /* 7337 : : + Find the next spill point. 7338 : : + It maybe be the next savepoint in the list, it may be the saved 7339 : : + start-of-statement point, or (if they coincide) it may be both. 7340 : : + It may also be the next max_len boundary, if len > max_len. 7341 : : + */ 7342 : : + my_off_t spill_pos; 7343 : : + void **next_stmt_start_ptr; 7344 : : + void **next_savepoint_ptr; 7345 : : + binlog_savepoint_info *next_sp; 7346 : 6212 : + if (do_stmt_pos && sp && stmt_pos == sp->cache_offset) 7347 : : + { 7348 : : + /* Double savepoint and start-of-statement point. */ 7349 : 390 : + spill_pos= stmt_pos; 7350 : 390 : + next_stmt_start_ptr= &mngr->stmt_start_engine_ptr; 7351 : 390 : + next_savepoint_ptr= &sp->engine_ptr; 7352 : 390 : + next_sp= sp->next; 7353 : : + } 7354 : 5822 : + else if (do_stmt_pos && (!sp || stmt_pos < sp->cache_offset)) 7355 : : + { 7356 : : + /* Spill the start-of-statement point next. */ 7357 : 1603 : + spill_pos= stmt_pos; 7358 : 1603 : + next_stmt_start_ptr= &mngr->stmt_start_engine_ptr; 7359 : 1603 : + next_savepoint_ptr= nullptr; 7360 : 1603 : + next_sp= sp; 7361 : : + } 7362 : 4219 : + else if (sp) 7363 : : + { 7364 : : + /* Spill the next savepoint now. */ 7365 : 338 : + spill_pos= sp->cache_offset; 7366 : 338 : + next_stmt_start_ptr= nullptr; 7367 : 338 : + next_savepoint_ptr= &sp->engine_ptr; 7368 : 338 : + next_sp= sp->next; 7369 : : + } 7370 : : + else 7371 : 3881 : + break; 7372 : 2331 : + DBUG_ASSERT(spill_pos >= spill_start); 7373 : 2331 : + if (spill_pos >= spill_end) 7374 : 2 : + break; 7375 : 2329 : + DBUG_ASSERT(spill_start + sofar <= spill_pos); 7376 : 2329 : + size_t part_len= spill_pos - (spill_start + sofar); 7377 : 2329 : + if (part_len > 0 || stmt_start_ptr || savepoint_ptr) 7378 : : + { 7379 : 1844 : + if (part_len > max_len) 7380 : : + { 7381 : : + /* Split this spill into smaller pieces. */ 7382 : 0 : + part_len= max_len; 7383 : 0 : + next_stmt_start_ptr= nullptr; 7384 : 0 : + next_savepoint_ptr= nullptr; 7385 : 0 : + next_sp= sp; 7386 : : + } 7387 : : + 7388 : 1844 : + mysql_mutex_lock(&LOCK_commit_ordered); 7389 : 1844 : + int res= (*opt_binlog_engine_hton->binlog_oob_data_ordered) 7390 : 1844 : + (mngr->thd, data + sofar, part_len, 7391 : 1844 : + engine_ptr, stmt_start_ptr, savepoint_ptr); 7392 : 1844 : + mysql_mutex_unlock(&LOCK_commit_ordered); 7393 : 1844 : + if (likely(!res)) 7394 : 3688 : + res= (*opt_binlog_engine_hton->binlog_oob_data) 7395 : 1844 : + (mngr->thd, data + sofar, part_len, engine_ptr); 7396 : 1844 : + if (unlikely(res)) 7397 : 0 : + return res; 7398 : 1844 : + sofar+= part_len; 7399 : : + } 7400 : : + 7401 : 2329 : + stmt_start_ptr= next_stmt_start_ptr; 7402 : 2329 : + savepoint_ptr= next_savepoint_ptr; 7403 : 2329 : + sp= next_sp; 7404 : 2329 : + if (stmt_start_ptr) 7405 : 1993 : + do_stmt_pos= false; /* Start-of-statement gets done now */ 7406 : 2329 : + } 7407 : 3883 : + mngr->cache_savepoint_list= sp; /* Remove any points spilled from cache. */ 7408 : 3883 : + if (likely(sp == nullptr)) 7409 : 3881 : + mngr->cache_savepoint_next_ptr= &mngr->cache_savepoint_list; 7410 : : + /* 7411 : : + We currently always spill the entire cache contents, which should mean 7412 : : + that at this point the remaining list of pending savepoints in the cache 7413 : : + is always empty - or possibly a savepoint at the current EOF. 7414 : : + Let's assert that this is so. However, if we ever want to partially 7415 : : + spill the cache and thus have remaining entries at this point, that is 7416 : : + fine, it is supported by the code and then this assertion can just be 7417 : : + removed. 7418 : : + */ 7419 : 3883 : + DBUG_ASSERT(sp == nullptr || sp->cache_offset == my_b_tell(cache)); 7420 : : + } 7421 : : + 7422 : 4977 : + DBUG_ASSERT(sofar < len); 7423 : : + do 7424 : : + { 7425 : 15451 : + size_t part_len= len - sofar; 7426 : 15451 : + if (part_len > max_len) 7427 : 10474 : + part_len= max_len; 7428 : 15451 : + mysql_mutex_lock(&LOCK_commit_ordered); 7429 : 15451 : + int res= (*opt_binlog_engine_hton->binlog_oob_data_ordered) 7430 : 15451 : + (mngr->thd, data + sofar, part_len, engine_ptr, 7431 : 15451 : + stmt_start_ptr, savepoint_ptr); 7432 : 15451 : + mysql_mutex_unlock(&LOCK_commit_ordered); 7433 : 15451 : + if (likely(!res)) 7434 : 30902 : + res= (*opt_binlog_engine_hton->binlog_oob_data) 7435 : 15451 : + (mngr->thd, data + sofar, part_len, engine_ptr); 7436 : 15451 : + if (unlikely(res)) 7437 : 0 : + return res; 7438 : 15451 : + stmt_start_ptr= nullptr; 7439 : 15451 : + savepoint_ptr= nullptr; 7440 : 15451 : + sofar+= part_len; 7441 : 15451 : + } while (sofar < len); 7442 : : + 7443 : 4977 : + cache_data->engine_binlog_info.out_of_band_offset+= len; 7444 : 4977 : + cache->pos_in_file= spill_end; 7445 : : + 7446 : 4977 : + return false; 7447 : : +} 7448 : : + 7449 : : + 7450 : : /* 7451 : : These functions are placed in this file since they need access to 7452 : : binlog_tp, which has internal linkage. 7457 : 63403 : auto *cache_mngr= (binlog_cache_mngr*) my_malloc(key_memory_binlog_cache_mngr, 7458 : : sizeof(binlog_cache_mngr), 7459 : : MYF(MY_ZEROFILL)); 7460 : 63407 : + if (!cache_mngr) 7461 : 0 : + return NULL; 7462 : 63407 : + IO_CACHE *stmt_cache= &cache_mngr->stmt_cache.cache_log; 7463 : : + my_bool res; 7464 : 63407 : + if (opt_binlog_engine_hton) 7465 : : + { 7466 : : + /* 7467 : : + With binlog implementation in engine, we do not need to spill large 7468 : : + transactions to temporary file, we will binlog data out-of-band spread 7469 : : + through the binlog as the transaction runs. Setting the file to INT_MIN 7470 : : + makes IO_CACHE not attempt to create the temporary file. 7471 : : + */ 7472 : 1971 : + res= init_io_cache(stmt_cache, (File)INT_MIN, 7473 : : + (size_t)binlog_stmt_cache_size, 7474 : : + WRITE_CACHE, 0L, 0, MYF(MY_WME | MY_NABP)); 7475 : : + /* 7476 : : + Use a custom write_function to spill to the engine-implemented binlog. 7477 : : + And re-use the IO_CACHE::append_read_pos as a handle for our 7478 : : + write_function; it is unused when the cache is not SEQ_READ_APPEND. 7479 : : + */ 7480 : 1971 : + stmt_cache->write_function= binlog_spill_to_engine; 7481 : 1971 : + stmt_cache->append_read_pos= (uchar *)cache_mngr; 7482 : : + } 7483 : : + else 7484 : 61436 : + res= open_cached_file(&cache_mngr->stmt_cache.cache_log, binlog_cache_dir, 7485 : : + LOG_PREFIX, (size_t)binlog_stmt_cache_size, 7486 : : + MYF(MY_WME) | MY_TRACK_WITH_LIMIT); 7487 : 63407 : + if (unlikely(res)) 7488 : : + { 7489 : 0 : + my_free(cache_mngr); 7490 : 0 : + return NULL; 7491 : : + } 7492 : 63407 : + IO_CACHE *trx_cache= &cache_mngr->trx_cache.cache_log; 7493 : 63407 : + if (opt_binlog_engine_hton) 7494 : : + { 7495 : 1971 : + res= init_io_cache(trx_cache, (File)INT_MIN, (size_t)binlog_cache_size, 7496 : : + WRITE_CACHE, 0L, 0, MYF(MY_WME | MY_NABP)); 7497 : 1971 : + trx_cache->write_function= binlog_spill_to_engine; 7498 : 1971 : + trx_cache->append_read_pos= (uchar *)cache_mngr; 7499 : : + } 7500 : : + else 7501 : 61436 : + res= open_cached_file(trx_cache, binlog_cache_dir, LOG_PREFIX, 7502 : : + (size_t)binlog_cache_size, 7503 : : + MYF(MY_WME | MY_TRACK_WITH_LIMIT)); 7504 : 63412 : + if (unlikely(res)) 7505 : : { 7506 : 0 : my_free(cache_mngr); 7507 : 0 : return NULL; 7516 : 63412 : bool precompute_checksums= 7517 : 63412 : !WSREP_NNULL(thd) && !encrypt_binlog && !opt_binlog_legacy_event_pos; 7518 : 0 : cache_mngr= new (cache_mngr) 7519 : : + binlog_cache_mngr(thd, max_binlog_stmt_cache_size, 7520 : : max_binlog_cache_size, 7521 : : &binlog_stmt_cache_use, 7522 : : &binlog_stmt_cache_disk_use, 7662 : 3 : server_id= wsrep_gtid_server.server_id; 7663 : : } 7664 : : Gtid_log_event gtid_event(this, seqno, domain_id, true, 7665 : : + Log_event::EVENT_NO_CACHE, 7666 : : + LOG_EVENT_SUPPRESS_USE_F, true, 0, 7667 : 7144 : + false, false); 7668 : 7144 : gtid_event.server_id= server_id; 7669 : 7144 : writer.write(>id_event); 7670 : 7144 : wsrep_write_cache_buf(&tmp_io_cache, &buf, &len); 7692 : 1237184 : } 7693 : : 7694 : 1186522 : void THD::binlog_set_stmt_begin() { 7695 : 1186522 : my_off_t pos= 0; 7696 : 1186522 : + binlog_cache_mngr *cache_mngr= binlog_setup_trx_data(); 7697 : 1186528 : + binlog_trans_log_savepos(this, cache_mngr, &pos); 7698 : 1186529 : cache_mngr->trx_cache.set_prev_position(pos); 7699 : 1186530 : } 7700 : : 7708 : : 7709 : : /* Server layer calls us with LOCK_commit_ordered locked, so this is safe. */ 7710 : 42 : mysql_mutex_assert_owner(&LOCK_commit_ordered); 7711 : 42 : + if (opt_binlog_engine_hton) 7712 : : + { 7713 : 2 : + (*opt_binlog_engine_hton->binlog_status) 7714 : 2 : + (&cache_mngr->last_commit_pos_file.engine_file_no, 7715 : : + &cache_mngr->last_commit_pos_offset); 7716 : : + } 7717 : : + else 7718 : : + { 7719 : 40 : + strmake_buf(cache_mngr->last_commit_pos_file.legacy_name, 7720 : : + mysql_bin_log.last_commit_pos_file); 7721 : 40 : + cache_mngr->last_commit_pos_offset= mysql_bin_log.last_commit_pos_offset; 7722 : : + } 7723 : : 7724 : 42 : trans_register_ha(thd, TRUE, &binlog_tp, 0); 7725 : : 8211 : : /* Generate a new global transaction ID, and write it to the binlog */ 8212 : : 8213 : : bool 8214 : 863823 : +MYSQL_BIN_LOG::write_gtid_event(THD *thd, binlog_cache_data *cache_data, 8215 : : + bool standalone, bool is_transactional, 8216 : : + uint64 commit_id, bool commit_by_rotate, 8217 : : bool has_xid, bool is_ro_1pc) 8218 : : { 8219 : : rpl_gtid gtid; 8267 : 863803 : if (thd->get_binlog_flags_for_alter() & Gtid_log_event::FL_START_ALTER_E1) 8268 : 15183 : thd->set_binlog_start_alter_seq_no(gtid.seq_no); 8269 : : 8270 : : + Log_event::enum_event_cache_type cache_type; 8271 : : + IO_CACHE *dest; 8272 : 863803 : + if (cache_data) 8273 : : + { 8274 : 138809 : + cache_type= cache_data->trx_cache() ? 8275 : : + Log_event::EVENT_TRANSACTIONAL_CACHE : Log_event::EVENT_STMT_CACHE; 8276 : 138809 : + dest= &cache_data->cache_log; 8277 : : + } 8278 : : + else 8279 : : + { 8280 : 724994 : + cache_type= Log_event::EVENT_NO_CACHE; 8281 : 724994 : + dest= &log_file; 8282 : : + } 8283 : : + Gtid_log_event gtid_event(thd, seq_no, domain_id, standalone, cache_type, 8284 : : LOG_EVENT_SUPPRESS_USE_F, is_transactional, 8285 : 863803 : commit_id, has_xid, is_ro_1pc); 8286 : : 8287 : 863803 : + if (opt_binlog_engine_hton) 8288 : : + { 8289 : 138809 : + DBUG_ASSERT(cache_data != nullptr); 8290 : 138809 : + uint32_t avail= (uint32_t)(dest->write_end - dest->write_pos); 8291 : 138845 : + if (unlikely(avail < Gtid_log_event::max_size) && 8292 : 36 : + avail < gtid_event.get_size()) 8293 : : + { 8294 : : + /* 8295 : : + The GTID event doesn't fit in the cache, so we have to spill the 8296 : : + contents as oob event data. 8297 : : + */ 8298 : 8 : + if (my_b_flush_io_cache(dest, 0)) 8299 : 0 : + DBUG_RETURN(true); 8300 : : + } 8301 : : + } 8302 : : + 8303 : : /* Write the event to the binary log. */ 8304 : 863803 : DBUG_ASSERT(this == &mysql_bin_log); 8305 : : 8314 : 863803 : if (unlikely(commit_by_rotate)) 8315 : 28 : gtid_event.pad_to_size= binlog_commit_by_rotate.get_gtid_event_pad_data_size(); 8316 : : 8317 : 863803 : + if (write_event(>id_event, cache_data, dest)) 8318 : 0 : DBUG_RETURN(true); 8319 : 863803 : status_var_add(thd->status_var.binlog_bytes_written, gtid_event.data_written); 8320 : : 8434 : : } 8435 : : 8436 : : 8437 : : +bool 8438 : 2144 : +load_global_binlog_state(rpl_binlog_state_base *state) 8439 : : +{ 8440 : 2144 : + mysql_mutex_lock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); 8441 : 2144 : + bool err= state->load_nolock(&rpl_global_gtid_binlog_state); 8442 : 2144 : + mysql_mutex_unlock(&rpl_global_gtid_binlog_state.LOCK_binlog_state); 8443 : 2144 : + return err; 8444 : : +} 8445 : : + 8446 : : + 8447 : : +/* 8448 : : + Used to initialize the binlog GTID state after restart when using 8449 : : + --binlog-storage-engine. The engine passes in the GTID binlog state it has 8450 : : + restored, and optionally passes a binlog reader. The intention is that the 8451 : : + engine can restore a state corresponding to a slightly earlier point in the 8452 : : + binlog file, and then we will use the reader to read any extra GTID events 8453 : : + and compute the final restored binlog GTID state from that. 8454 : : +*/ 8455 : : +bool 8456 : 47 : +binlog_recover_gtid_state(rpl_binlog_state_base *state, 8457 : : + handler_binlog_reader *reader) 8458 : : +{ 8459 : 47 : + String packet; 8460 : 47 : + Format_description_log_event fd_event(4); 8461 : : + 8462 : 47 : + if (reader) 8463 : : + { 8464 : : + for (;;) 8465 : : + { 8466 : 6405 : + packet.length(0); 8467 : 6405 : + int err= reader->read_log_event(&packet, 0, MAX_MAX_ALLOWED_PACKET); 8468 : 6405 : + if (err == LOG_READ_EOF) 8469 : 47 : + break; 8470 : 6358 : + if (err) 8471 : 0 : + return true; 8472 : : + Log_event_type event_type= 8473 : 6358 : + (Log_event_type)((uchar)packet[EVENT_TYPE_OFFSET]); 8474 : 6358 : + if (event_type != GTID_EVENT) 8475 : 5300 : + continue; 8476 : : + rpl_gtid gtid; 8477 : : + uchar flags2; 8478 : 1058 : + if (Gtid_log_event::peek((uchar*) packet.ptr(), packet.length(), 8479 : : + BINLOG_CHECKSUM_ALG_OFF, >id.domain_id, 8480 : : + >id.server_id, >id.seq_no, &flags2, 8481 : : + &fd_event)) 8482 : 0 : + return true; 8483 : 1058 : + state->update_nolock(>id); 8484 : 6358 : + } 8485 : : + } 8486 : 47 : + rpl_global_gtid_binlog_state.load_nolock(state); 8487 : 47 : + return false; 8488 : 47 : +} 8489 : : + 8490 : : + 8491 : : bool 8492 : 85878 : MYSQL_BIN_LOG::append_state_pos(String *str) 8493 : : { 8570 : 945758 : bool is_trans_cache= FALSE; 8571 : 945758 : bool using_trans= event_info->use_trans_cache(); 8572 : 945757 : bool direct= event_info->use_direct_logging(); 8573 : : + bool events_direct; 8574 : 945760 : ulong UNINIT_VAR(prev_binlog_id); 8575 : 945760 : + uint64 UNINIT_VAR(commit_id); 8576 : : + const rpl_gtid *commit_gtid; 8577 : 945760 : DBUG_ENTER("MYSQL_BIN_LOG::write(Log_event *)"); 8578 : : 8579 : : /* 8660 : 225774 : DBUG_RETURN(0); 8661 : : #endif /* HAVE_REPLICATION */ 8662 : : 8663 : 700173 : + binlog_cache_mngr * cache_mngr= NULL; 8664 : 700173 : IO_CACHE *file= NULL; 8665 : : 8666 : 700173 : + events_direct= direct; 8667 : 700173 : if (direct) 8668 : : { 8669 : : + /* Write the event to the binlog immediately. */ 8670 : : int res; 8671 : : + 8672 : 193384 : DBUG_PRINT("info", ("direct is set")); 8673 : 193388 : DBUG_ASSERT(!thd->backup_commit_lock); 8674 : 193388 : + commit_id= 0; 8675 : 193388 : DBUG_EXECUTE_IF("binlog_force_commit_id", 8676 : : { 8677 : : const LEX_CSTRING commit_name= { STRING_WITH_LEN("commit_id") }; 8682 : : commit_name.length); 8683 : : commit_id= entry->val_int(&null_value); 8684 : : }); 8685 : : + 8686 : 193388 : + if (opt_binlog_engine_hton) 8687 : : + { 8688 : 4228 : + events_direct= false; 8689 : 4228 : + if (!(cache_mngr= thd->binlog_setup_trx_data())) 8690 : 0 : + DBUG_RETURN(1); 8691 : 4228 : + cache_data= cache_mngr->get_binlog_cache_data(false); 8692 : 4228 : + DBUG_ASSERT(cache_data->empty()); 8693 : 4228 : + file= &cache_data->cache_log; 8694 : : + /* Set cache_type to ensure we don't get checksums for this event */ 8695 : 4228 : + event_info->cache_type= Log_event::EVENT_STMT_CACHE; 8696 : : + } 8697 : : + else 8698 : : + { 8699 : 189160 : + MDL_request mdl_request; 8700 : : + 8701 : 189160 : + MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_COMMIT, 8702 : : + MDL_EXPLICIT); 8703 : 189160 : + if (thd->mdl_context.acquire_lock(&mdl_request, 8704 : 189160 : + thd->variables.lock_wait_timeout)) 8705 : 6 : + DBUG_RETURN(1); 8706 : 189154 : + thd->backup_commit_lock= &mdl_request; 8707 : : + 8708 : 189154 : + if ((res= thd->wait_for_prior_commit())) 8709 : : + { 8710 : 0 : + if (mdl_request.ticket) 8711 : 0 : + thd->mdl_context.release_lock(mdl_request.ticket); 8712 : 0 : + thd->backup_commit_lock= 0; 8713 : 0 : + DBUG_RETURN(res); 8714 : : + } 8715 : 189153 : + file= &log_file; 8716 : 189153 : + my_org_b_tell= my_b_tell(file); 8717 : 189152 : + mysql_mutex_lock(&LOCK_log); 8718 : 189154 : + prev_binlog_id= current_binlog_id; 8719 : 189154 : + res= write_gtid_event(thd, nullptr, true, using_trans, commit_id, 8720 : : + false, false, false); 8721 : 189154 : + if (mdl_request.ticket) 8722 : 189154 : + thd->mdl_context.release_lock(mdl_request.ticket); 8723 : 189154 : + thd->backup_commit_lock= 0; 8724 : 189154 : + if (res) 8725 : 2 : + goto err; 8726 : : + } 8727 : : } 8728 : : else 8729 : : { 8730 : : + /* Write the event to the stmt or trx cache, and binlog it later. */ 8731 : 506789 : + if (!(cache_mngr= thd->binlog_setup_trx_data())) 8732 : 0 : goto err; 8733 : : 8734 : 506791 : is_trans_cache= use_trans_cache(thd, using_trans); 8753 : 700170 : if (with_annotate && *with_annotate) 8754 : : { 8755 : 0 : DBUG_ASSERT(event_info->get_type_code() == TABLE_MAP_EVENT); 8756 : 0 : + Annotate_rows_log_event anno(thd, using_trans, events_direct); 8757 : : /* Annotate event should be written not more than once */ 8758 : 0 : *with_annotate= 0; 8759 : 0 : if (write_event(&anno, cache_data, file)) 8767 : : { 8768 : : Intvar_log_event e(thd,(uchar) LAST_INSERT_ID_EVENT, 8769 : : thd->first_successful_insert_id_in_prev_stmt_for_binlog, 8770 : 4427 : + using_trans, events_direct); 8771 : 4427 : if (write_event(&e, cache_data, file)) 8772 : 0 : goto err; 8773 : 4427 : } 8778 : : nb_elements())); 8779 : : Intvar_log_event e(thd, (uchar) INSERT_ID_EVENT, 8780 : : thd->auto_inc_intervals_in_cur_stmt_for_binlog. 8781 : 58758 : + minimum(), using_trans, events_direct); 8782 : 58758 : if (write_event(&e, cache_data, file)) 8783 : 0 : goto err; 8784 : 58758 : } 8785 : 643299 : if (thd->used & THD::RAND_USED) 8786 : : { 8787 : 534 : Rand_log_event e(thd,thd->rand_saved_seed1,thd->rand_saved_seed2, 8788 : 534 : + using_trans, events_direct); 8789 : 534 : if (write_event(&e, cache_data, file)) 8790 : 0 : goto err; 8791 : 534 : } 8803 : 0 : user_var_event->th->user_var_log_event_data_type( 8804 : : user_var_event->charset_number), 8805 : : using_trans, 8806 : 45224 : + events_direct); 8807 : 45224 : if (write_event(&e, cache_data, file)) 8808 : 0 : goto err; 8809 : 45224 : } 8822 : 700176 : err: 8823 : 700176 : if (direct) 8824 : : { 8825 : 193382 : DBUG_ASSERT(!is_relay_log); 8826 : 193382 : + if (opt_binlog_engine_hton) 8827 : : { 8828 : 4228 : + handler_binlog_event_group_info *engine_context= 8829 : : + &cache_data->engine_binlog_info; 8830 : 4228 : + engine_context->gtid_offset= my_b_tell(file); 8831 : : + my_off_t binlog_total_bytes; 8832 : 4228 : + MDL_request mdl_request; 8833 : : + int res; 8834 : : + 8835 : 4228 : + if (engine_context->out_of_band_offset) 8836 : : + { 8837 : : + /* 8838 : : + If we spilled part of the event data as oob, then we have to spill 8839 : : + all of it. 8840 : : + */ 8841 : 4 : + if (my_b_flush_io_cache(file, 0)) 8842 : 0 : + goto engine_fail; 8843 : : + } 8844 : : 8845 : 4228 : + MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_COMMIT, 8846 : : + MDL_EXPLICIT); 8847 : 4228 : + if (thd->mdl_context.acquire_lock(&mdl_request, 8848 : 4228 : + thd->variables.lock_wait_timeout)) 8849 : 0 : + goto engine_fail; 8850 : 4228 : + thd->backup_commit_lock= &mdl_request; 8851 : : 8852 : 4228 : + if (thd->wait_for_prior_commit()) 8853 : : { 8854 : 0 : + if (mdl_request.ticket) 8855 : 0 : + thd->mdl_context.release_lock(mdl_request.ticket); 8856 : 0 : + thd->backup_commit_lock= 0; 8857 : 0 : + goto engine_fail; 8858 : : } 8859 : 4228 : + mysql_mutex_lock(&LOCK_log); 8860 : 4228 : + res= write_gtid_event(thd, cache_data, true, using_trans, commit_id, 8861 : : + false, false, false); 8862 : 4228 : + if (mdl_request.ticket) 8863 : 4228 : + thd->mdl_context.release_lock(mdl_request.ticket); 8864 : 4228 : + thd->backup_commit_lock= 0; 8865 : 4228 : + if (res) 8866 : 0 : + goto engine_fail; 8867 : : + 8868 : 4228 : + binlog_total_bytes= my_b_bytes_in_cache(file); 8869 : : + /* 8870 : : + Engine-in-binlog does not support the after-sync method. 8871 : : + This is for consistency with the binlogging of transactions in the 8872 : : + engine, which commit atomically at the same time in binlog and engine. 8873 : : + 8874 : : + In any case, for non-transactional event group (eg. DDL), the 8875 : : + after-sync and after-commit semisync methods are mostly the same; the 8876 : : + change has already become visible to other connections on the master 8877 : : + when it is binlogged. 8878 : : + 8879 : : + ToDo: If semi-sync is enabled, obtain the binlog coords from the 8880 : : + engine to be waited for later at after-commit. 8881 : : + */ 8882 : 4228 : + mysql_mutex_lock(&LOCK_after_binlog_sync); 8883 : 4228 : + mysql_mutex_unlock(&LOCK_log); 8884 : 4228 : + mysql_mutex_lock(&LOCK_commit_ordered); 8885 : 4228 : + mysql_mutex_unlock(&LOCK_after_binlog_sync); 8886 : 4228 : + commit_gtid= thd->get_last_commit_gtid(); 8887 : 4228 : + if (unlikely((*opt_binlog_engine_hton->binlog_write_direct_ordered) 8888 : : + (file, engine_context, commit_gtid))) 8889 : : { 8890 : 0 : + mysql_mutex_unlock(&LOCK_commit_ordered); 8891 : 0 : + goto engine_fail; 8892 : : + } 8893 : 4228 : + mysql_mutex_unlock(&LOCK_commit_ordered); 8894 : 4228 : + cache_mngr->last_commit_pos_file.engine_file_no= 8895 : 4228 : + engine_context->out_file_no; 8896 : 4228 : + cache_mngr->last_commit_pos_offset= engine_context->out_file_no; 8897 : : + 8898 : 4228 : + if (unlikely((*opt_binlog_engine_hton->binlog_write_direct) 8899 : : + (file, engine_context, commit_gtid))) 8900 : 0 : + goto engine_fail; 8901 : 4228 : + status_var_add(thd->status_var.binlog_bytes_written, binlog_total_bytes); 8902 : : + 8903 : 4228 : + goto engine_ok; 8904 : 0 : + engine_fail: 8905 : 0 : + error= 1; 8906 : 4228 : + engine_ok: 8907 : 4228 : + cache_mngr->reset(true, false); 8908 : : + } 8909 : : + else 8910 : : + { 8911 : 189154 : + my_off_t offset= my_b_tell(file); 8912 : 189154 : + bool check_purge= false; 8913 : : + 8914 : 189154 : + if (likely(!error)) 8915 : : + { 8916 : : + bool synced; 8917 : : + 8918 : 189062 : + update_gtid_index((uint32)offset, thd->get_last_commit_gtid()); 8919 : : + 8920 : 189062 : + if ((error= flush_and_sync(&synced))) 8921 : : { 8922 : : } 8923 : : else 8924 : : { 8925 : 189062 : + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); 8926 : 189062 : + mysql_mutex_assert_owner(&LOCK_log); 8927 : 189062 : + mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); 8928 : 189062 : + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); 8929 : : +#ifdef HAVE_REPLICATION 8930 : 189062 : + if (repl_semisync_master.report_binlog_update(thd, thd, 8931 : 189062 : + log_file_name, offset)) 8932 : : + { 8933 : 0 : + sql_print_error("Failed to run 'after_flush' hooks"); 8934 : 0 : + error= 1; 8935 : : + } 8936 : : + else 8937 : : +#endif 8938 : : + { 8939 : : + /* 8940 : : + update binlog_end_pos so it can be read by dump thread 8941 : : + note: must be _after_ the RUN_HOOK(after_flush) or else 8942 : : + semi-sync might not have put the transaction into 8943 : : + it's list before dump-thread tries to send it 8944 : : + */ 8945 : 189062 : + update_binlog_end_pos(offset); 8946 : 189062 : + if (unlikely((error= rotate(false, &check_purge)))) 8947 : 0 : + check_purge= false; 8948 : : + } 8949 : : } 8950 : : } 8951 : : 8952 : 189154 : + status_var_add(thd->status_var.binlog_bytes_written, 8953 : : + offset - my_org_b_tell); 8954 : : 8955 : 189154 : + mysql_mutex_lock(&LOCK_after_binlog_sync); 8956 : 189154 : + mysql_mutex_unlock(&LOCK_log); 8957 : : 8958 : 189154 : + DEBUG_SYNC(thd, "commit_after_release_LOCK_log"); 8959 : : 8960 : 189154 : + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); 8961 : 189154 : + mysql_mutex_assert_not_owner(&LOCK_log); 8962 : 189154 : + mysql_mutex_assert_owner(&LOCK_after_binlog_sync); 8963 : 189154 : + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); 8964 : : #ifdef HAVE_REPLICATION 8965 : 189154 : + if (repl_semisync_master.wait_after_sync(log_file_name, offset)) 8966 : : + { 8967 : 0 : + error=1; 8968 : : + /* error is already printed inside hook */ 8969 : : + } 8970 : : #endif 8971 : : 8972 : : + /* 8973 : : + Take mutex to protect against a reader seeing partial writes of 64-bit 8974 : : + offset on 32-bit CPUs. 8975 : : + */ 8976 : 189154 : + mysql_mutex_lock(&LOCK_commit_ordered); 8977 : 189154 : + mysql_mutex_unlock(&LOCK_after_binlog_sync); 8978 : 189154 : + last_commit_pos_offset= offset; 8979 : 189154 : + mysql_mutex_unlock(&LOCK_commit_ordered); 8980 : : 8981 : 189154 : + if (check_purge) 8982 : 4 : + checkpoint_and_purge(prev_binlog_id); 8983 : : + } 8984 : : } 8985 : : 8986 : 700176 : if (unlikely(error)) 8997 : : 8998 : : 8999 : : void 9000 : 725074 : +MYSQL_BIN_LOG::update_gtid_index(uint32 offset, const rpl_gtid *gtid) 9001 : : { 9002 : 725074 : if (!unlikely(gtid_index)) 9003 : 0 : return; 9004 : : 9005 : : rpl_gtid *gtid_list; 9006 : : uint32 gtid_count; 9007 : 725074 : + int err= gtid_index->process_gtid_check_batch(offset, gtid, 9008 : : >id_list, >id_count); 9009 : 725074 : if (err) 9010 : 0 : return; 9172 : 730625 : int error= 0; 9173 : : ulonglong binlog_pos; 9174 : 730625 : DBUG_ENTER("MYSQL_BIN_LOG::rotate"); 9175 : 730625 : + DBUG_ASSERT(!opt_binlog_engine_hton); 9176 : : 9177 : : #ifdef WITH_WSREP 9178 : 730625 : if (WSREP_ON && wsrep_to_isolation) 9360 : : IO_CACHE cache; 9361 : 7643 : const char* errmsg= NULL; 9362 : 7643 : char errbuf[MYSQL_ERRMSG_SIZE]= {0}; 9363 : 7643 : + rpl_binlog_state_base init_state; 9364 : : 9365 : 7643 : if (!domain_drop_lex) 9366 : 7607 : return 0; // still "effective" having empty domain sequence to delete 9381 : : errmsg= "injected error";); 9382 : 36 : if (errmsg) 9383 : 2 : goto end; 9384 : : + 9385 : 34 : + init_state.init(); 9386 : 34 : + if (init_state.load_nolock(glev->list, glev->count)) 9387 : : + { 9388 : 0 : + my_error(ER_OUT_OF_RESOURCES, MYF(0)); 9389 : 0 : + rc= -1; 9390 : 0 : + goto err; 9391 : : + } 9392 : 34 : errmsg= rpl_global_gtid_binlog_state.drop_domain(domain_drop_lex, 9393 : : + &init_state, errbuf); 9394 : : 9395 : 36 : end: 9396 : 36 : if (errmsg) 9405 : 6 : rc= 1; 9406 : : } 9407 : : } 9408 : 20 : +err: 9409 : 36 : delete glev; 9410 : : 9411 : 36 : return rc; 9465 : 7619 : DBUG_RETURN(error); 9466 : 7619 : } 9467 : : 9468 : : + 9469 : : +/** 9470 : : + Remove a list of domains from the in-memory global binlog state, after 9471 : : + checking that deletion is safe. "Safe" in this context means that there 9472 : : + are no GTID present with the domain in any of the existing binlog files 9473 : : + (ie. the binlog files where that domain was used have all been purged). 9474 : : + This is checked by comparing the binlog state at the beginning of the 9475 : : + earliest current binlog file with the current binlog state. 9476 : : + 9477 : : + @param domain_drop_lex gtid domain id sequence from lex. 9478 : : + Passed as a pointer to dynamic array must be not empty 9479 : : + unless pointer value NULL. 9480 : : + @retval zero on success 9481 : : + @retval > 0 ineffective call none from the *non* empty 9482 : : + gtid domain sequence is deleted 9483 : : + @retval < 0 on error 9484 : : +*/ 9485 : : +static int 9486 : 432 : +binlog_engine_delete_gtid_domain(DYNAMIC_ARRAY *domain_drop_lex) 9487 : : +{ 9488 : 432 : + int rc= 0; 9489 : 432 : + const char* errmsg= NULL; 9490 : 432 : + char errbuf[MYSQL_ERRMSG_SIZE]= {0}; 9491 : 432 : + rpl_binlog_state_base init_state; 9492 : : + 9493 : 432 : + if (!domain_drop_lex) 9494 : 426 : + return 0; // still "effective" having empty domain sequence to delete 9495 : : + 9496 : 6 : + DBUG_ASSERT(domain_drop_lex->elements > 0); 9497 : 6 : + DBUG_ASSERT(opt_binlog_engine_hton); 9498 : 6 : + mysql_mutex_assert_owner(&LOCK_commit_ordered); 9499 : : + 9500 : 6 : + if (!opt_binlog_engine_hton->binlog_get_init_state) 9501 : : + { 9502 : 0 : + my_error(ER_ENGINE_BINLOG_NO_DELETE_DOMAIN, MYF(0)); 9503 : 0 : + return -1; 9504 : : + } 9505 : : + 9506 : 6 : + init_state.init(); 9507 : 6 : + if ((*opt_binlog_engine_hton->binlog_get_init_state)(&init_state)) 9508 : : + { 9509 : 0 : + my_error(ER_BINLOG_CANNOT_READ_STATE, MYF(0)); 9510 : 0 : + return -1; 9511 : : + } 9512 : 6 : + errmsg= rpl_global_gtid_binlog_state.drop_domain(domain_drop_lex, 9513 : : + &init_state, errbuf); 9514 : 6 : + if (errmsg) 9515 : : + { 9516 : 4 : + if (strlen(errmsg) > 0) 9517 : : + { 9518 : 2 : + my_error(ER_BINLOG_CANT_DELETE_GTID_DOMAIN, MYF(0), errmsg); 9519 : 2 : + rc= -1; 9520 : : + } 9521 : : + else 9522 : : + { 9523 : 2 : + rc= 1; 9524 : : + } 9525 : : + } 9526 : 6 : + return rc; 9527 : 432 : +} 9528 : : + 9529 : : + 9530 : : +/* Implementation of FLUSH BINARY LOGS for binlog implemented in engine. */ 9531 : : +int 9532 : 432 : +MYSQL_BIN_LOG::flush_binlogs_engine(DYNAMIC_ARRAY *domain_drop_lex) 9533 : : +{ 9534 : 432 : + int error= 0; 9535 : 432 : + DBUG_ENTER("MYSQL_BIN_LOG::flush_binlogs_engine"); 9536 : : + 9537 : 432 : + mysql_mutex_lock(&LOCK_log); 9538 : 432 : + mysql_mutex_lock(&LOCK_after_binlog_sync); 9539 : 432 : + mysql_mutex_unlock(&LOCK_log); 9540 : 432 : + mysql_mutex_lock(&LOCK_commit_ordered); 9541 : 432 : + mysql_mutex_unlock(&LOCK_after_binlog_sync); 9542 : : + 9543 : 432 : + if ((error= binlog_engine_delete_gtid_domain(domain_drop_lex))) 9544 : : + { 9545 : 4 : + if (error < 0) 9546 : 2 : + error= 1; 9547 : : + else 9548 : : + { 9549 : : + /* 9550 : : + If the domain(s) specified were already deleted, then a warning was 9551 : : + sent (by rpl_binlog_state::drop_domain()), but the statement succeeds 9552 : : + anyway and the FLUSH to move to a new file is still done. 9553 : : + 9554 : : + (This is inconsistent with the legacy behaviour, which succeeds the 9555 : : + statement with a warning but _skips_ the flush/binlog rotation. It 9556 : : + seems a more reasonable behaviour that a FLUSH BINARY LOGS statement 9557 : : + _either_ performs the FLUSH, _or_ fails with an error, so this is 9558 : : + what we do in the engine binlog implementation). 9559 : : + */ 9560 : 2 : + error= 0; 9561 : : + } 9562 : : + } 9563 : 432 : + if (!error && (*opt_binlog_engine_hton->binlog_flush)()) 9564 : 0 : + error= 1; 9565 : : + 9566 : 432 : + mysql_mutex_unlock(&LOCK_commit_ordered); 9567 : : + 9568 : 432 : + DBUG_RETURN(error); 9569 : 432 : +} 9570 : : + 9571 : : + 9572 : 2855 : uint MYSQL_BIN_LOG::next_file_id() 9573 : : { 9574 : : uint res; 9627 : 508032 : IO_CACHE *cache= &cache_data->cache_log; 9628 : 508032 : DBUG_ENTER("Event_log::write_cache"); 9629 : : 9630 : 508032 : + DBUG_ASSERT(!opt_binlog_engine_hton); 9631 : 508032 : mysql_mutex_assert_owner(&LOCK_log); 9632 : : 9633 : 508032 : if (cache_data->init_for_read()) 9797 : 218 : Incident incident= INCIDENT_LOST_EVENTS; 9798 : 218 : Incident_log_event ev(thd, incident, &write_error_msg); 9799 : : 9800 : 218 : + DBUG_ASSERT(!opt_binlog_engine_hton); 9801 : 218 : if (likely(is_open())) 9802 : : { 9803 : 164 : error= write_event(&ev); 9816 : : ulong prev_binlog_id; 9817 : 48 : DBUG_ENTER("MYSQL_BIN_LOG::write_incident"); 9818 : : 9819 : 48 : + if (opt_binlog_engine_hton) 9820 : 0 : + DBUG_RETURN(0); 9821 : : + 9822 : 48 : mysql_mutex_lock(&LOCK_log); 9823 : 48 : if (likely(is_open())) 9824 : : { 9881 : : bool err; 9882 : 8139 : Binlog_checkpoint_log_event ev(name_arg, len); 9883 : : 9884 : 8139 : + DBUG_ASSERT(!opt_binlog_engine_hton); 9885 : : /* 9886 : : Note that we must sync the binlog checkpoint to disk. 9887 : : Otherwise a subsequent log purge could delete binlogs that XA recovery 9969 : : ) 9970 : : { 9971 : 1828 : cache_mngr->need_unlog= false; 9972 : 1828 : + cache_mngr->need_engine_2pc= false; 9973 : 1828 : DBUG_RETURN(0); 9974 : : } 9975 : : 9977 : 673419 : entry.cache_mngr= cache_mngr; 9978 : 673419 : entry.error= 0; 9979 : 673419 : entry.all= all; 9980 : 673419 : + entry.need_unlog= unlikely(is_preparing_xa(thd)) && !opt_binlog_engine_hton; 9981 : 673422 : ha_info= all ? thd->transaction->all.ha_list : thd->transaction->stmt.ha_list; 9982 : 673422 : entry.ro_1pc= is_ro_1pc; 9983 : 673422 : + entry.do_binlog_group_commit_ordered= false; 9984 : 673422 : entry.end_event= end_ev; 9985 : 673422 : + cache_mngr->using_stmt_cache= using_stmt_cache; 9986 : 673422 : + cache_mngr->using_trx_cache= using_trx_cache; 9987 : 673422 : + cache_mngr->need_engine_2pc= false; 9988 : 673422 : + auto has_xid= entry.end_event->get_type_code() == XID_EVENT; 9989 : : 9990 : 1464012 : + for (; ha_info; ha_info= ha_info->next()) 9991 : : { 9992 : 1055096 : + if (likely(has_xid) && likely(ha_info->is_started())) 9993 : : { 9994 : 790591 : + if (opt_binlog_engine_hton) 9995 : : { 9996 : 271937 : + if (ha_info->ht() != &binlog_tp && 9997 : 271982 : + ha_info->ht() != opt_binlog_engine_hton && 9998 : 46 : + ha_info->is_trx_read_write()) 9999 : : + { 10000 : 42 : + cache_mngr->need_engine_2pc= true; 10001 : 42 : + cache_mngr->trx_cache.engine_binlog_info.internal_xa= true; 10002 : : + } 10003 : : + } 10004 : : + else 10005 : : + { 10006 : 518654 : + if (ha_info->ht() != &binlog_tp && 10007 : 777672 : + ha_info->is_trx_read_write() && 10008 : 259021 : + !ha_info->ht()->commit_checkpoint_request) 10009 : 344 : + entry.need_unlog= true; 10010 : : } 10011 : : } 10012 : : + else 10013 : 264506 : + break; 10014 : : } 10015 : : 10016 : 1346837 : if (cache_mngr->stmt_cache.has_incident() || 10410 : 673385 : MYSQL_BIN_LOG::write_transaction_with_group_commit(group_commit_entry *entry) 10411 : : { 10412 : 673385 : int is_leader= queue_for_group_commit(entry); 10413 : 673442 : + binlog_cache_mngr *cache_mngr= entry->cache_mngr; 10414 : : 10415 : : #ifdef WITH_WSREP 10416 : : /* commit order was released in queue_for_group_commit() call, 10470 : : 10471 : 58 : DEBUG_SYNC(entry->thd, "commit_loop_entry_commit_ordered"); 10472 : 56 : ++num_commits; 10473 : 56 : + if (cache_mngr->using_xa && !entry->error) 10474 : 56 : run_commit_ordered(entry->thd, entry->all); 10475 : : 10476 : 56 : + if (unlikely(!cache_mngr->engine_binlogged) && opt_binlog_engine_hton) 10477 : : + { 10478 : 0 : + binlog_cache_data *cache_data= cache_mngr->engine_cache_data(); 10479 : 0 : + IO_CACHE *file= &cache_data->cache_log; 10480 : 0 : + handler_binlog_event_group_info *engine_context= 10481 : : + &cache_data->engine_binlog_info; 10482 : 0 : + binlog_setup_engine_commit_data(engine_context, cache_mngr); 10483 : 0 : + if (likely(!entry->error)) 10484 : : + { 10485 : 0 : + entry->error= (*opt_binlog_engine_hton->binlog_write_direct_ordered) 10486 : 0 : + (file, engine_context, entry->thd->get_last_commit_gtid()); 10487 : 0 : + if (likely(!entry->error)) 10488 : : + { 10489 : 0 : + cache_mngr->last_commit_pos_file.engine_file_no= 10490 : 0 : + engine_context->out_file_no; 10491 : 0 : + cache_mngr->last_commit_pos_offset= engine_context->out_file_no; 10492 : : + 10493 : : + /* Mark to call binlog_write_direct() later. */ 10494 : 0 : + cache_mngr->need_write_direct= TRUE; 10495 : : + } 10496 : : + } 10497 : : + } 10498 : : + 10499 : 56 : group_commit_entry *next= entry->next; 10500 : 56 : if (!next) 10501 : : { 10537 : : 10538 : : } 10539 : : 10540 : 670542 : + if (unlikely(cache_mngr->need_write_direct)) 10541 : : + { 10542 : 588 : + binlog_cache_data *cache_data= cache_mngr->engine_cache_data(); 10543 : 588 : + IO_CACHE *file= &cache_data->cache_log; 10544 : 588 : + handler_binlog_event_group_info *engine_context= 10545 : : + &cache_data->engine_binlog_info; 10546 : 588 : + if (likely(!entry->error)) 10547 : 1176 : + entry->error= (*opt_binlog_engine_hton->binlog_write_direct) 10548 : 588 : + (file, engine_context, entry->thd->get_last_commit_gtid()); 10549 : : + } 10550 : 670542 : + if (entry->do_binlog_group_commit_ordered) 10551 : : + { 10552 : : + binlog_cache_data *cache_data= 10553 : 131063 : + cache_mngr->get_binlog_cache_data(cache_mngr->using_trx_cache); 10554 : 131063 : + (*opt_binlog_engine_hton->binlog_group_commit_ordered) 10555 : 131063 : + (entry->thd, &cache_data->engine_binlog_info); 10556 : : + } 10557 : : + 10558 : 670536 : if (likely(!entry->error)) 10559 : 670474 : return entry->thd->wait_for_prior_commit(); 10560 : : else 10713 : : current, commit_id, commit_by_rotate)))) 10714 : 62 : current->commit_errno= errno; 10715 : : 10716 : 670593 : + if (!opt_binlog_engine_hton) 10717 : : + { 10718 : 536012 : + strmake_buf(cache_mngr->last_commit_pos_file.legacy_name, log_file_name); 10719 : 536012 : + commit_offset= my_b_write_tell(&log_file); 10720 : 536012 : + cache_mngr->last_commit_pos_offset= commit_offset; 10721 : : + /* 10722 : : + When --binlog-storage-engine, the last_commit_pos is updated in 10723 : : + binlog_get_cache(). 10724 : : + */ 10725 : 536012 : + update_gtid_index((uint32)commit_offset, 10726 : 536012 : + current->thd->get_last_commit_gtid()); 10727 : : + } 10728 : : + 10729 : 670593 : if ((cache_mngr->using_xa && cache_mngr->xa_xid) || current->need_unlog) 10730 : : { 10731 : : /* 10749 : 664643 : set_current_thd(leader->thd); 10750 : : 10751 : 664643 : bool synced= 0; 10752 : 664643 : + if (!opt_binlog_engine_hton && unlikely(flush_and_sync(&synced))) 10753 : : { 10754 : 0 : for (current= leader; current != NULL; current= current->next) 10755 : : { 10763 : : } 10764 : : else 10765 : : { 10766 : : +#ifdef HAVE_REPLICATION 10767 : 664643 : + if (unlikely(repl_semisync_master.get_master_enabled())) 10768 : : + { 10769 : 9298 : + DEBUG_SYNC(leader->thd, "commit_before_update_binlog_end_pos"); 10770 : 9298 : + bool any_error= false; 10771 : : 10772 : 9298 : + mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); 10773 : 9298 : + mysql_mutex_assert_owner(&LOCK_log); 10774 : 9298 : + mysql_mutex_assert_not_owner(&LOCK_after_binlog_sync); 10775 : 9298 : + mysql_mutex_assert_not_owner(&LOCK_commit_ordered); 10776 : : 10777 : 18736 : + for (current= leader; current != NULL; current= current->next) 10778 : : { 10779 : : + /* 10780 : : + The thread which will await the ACK from the replica can change 10781 : : + depending on the wait-point. If AFTER_COMMIT, then the user thread 10782 : : + will perform the wait. If AFTER_SYNC, the binlog group commit leader 10783 : : + will perform the wait on behalf of the user thread. 10784 : : + */ 10785 : 9438 : + THD *waiter_thd= (repl_semisync_master.wait_point() == 10786 : : + SEMI_SYNC_MASTER_WAIT_POINT_AFTER_STORAGE_COMMIT) 10787 : 9438 : + ? current->thd 10788 : 9438 : + : leader->thd; 10789 : : + char buf[FN_REFLEN]; 10790 : 9438 : + const char *filename= buf; 10791 : 9438 : + if (opt_binlog_engine_hton) 10792 : 0 : + (*opt_binlog_engine_hton->get_filename) 10793 : 0 : + (buf, current->cache_mngr->last_commit_pos_file.engine_file_no); 10794 : : + else 10795 : 9438 : + filename= current->cache_mngr->last_commit_pos_file.legacy_name; 10796 : 18876 : + if (likely(!current->error) && 10797 : 9438 : + unlikely(repl_semisync_master. 10798 : : + report_binlog_update(current->thd, waiter_thd, filename, 10799 : : + (my_off_t)current->cache_mngr-> 10800 : : + last_commit_pos_offset))) 10801 : : + { 10802 : 0 : + current->error= ER_ERROR_ON_WRITE; 10803 : 0 : + current->commit_errno= -1; 10804 : 0 : + current->error_cache= NULL; 10805 : 0 : + any_error= true; 10806 : : + } 10807 : : } 10808 : : + 10809 : 9298 : + if (unlikely(any_error)) 10810 : 0 : + sql_print_error("Failed to run 'after_flush' hooks"); 10811 : : } 10812 : : +#endif 10813 : : 10814 : : /* 10815 : : update binlog_end_pos so it can be read by dump thread 10816 : : Note: must be _after_ the RUN_HOOK(after_flush) or else 10817 : : semi-sync might not have put the transaction into 10818 : : it's list before dump-thread tries to send it 10819 : : 10820 : : + When --binlog-storage-engine, the binlog write happens during 10821 : : + commit_ordered(), so postpone the update until then. 10822 : : + */ 10823 : 664643 : + if (!opt_binlog_engine_hton) 10824 : 533580 : + update_binlog_end_pos(commit_offset); 10825 : : } 10826 : : 10827 : 664643 : + if (!opt_binlog_engine_hton) 10828 : : { 10829 : : /* 10830 : : + If any commit_events are Xid_log_event, increase the number of pending 10831 : : + XIDs in current binlog (it's decreased in ::unlog()). When the count in 10832 : : + a (not active) binlog file reaches zero, we know that it is no longer 10833 : : + needed in XA recovery, and we can log a new binlog checkpoint event. 10834 : : + */ 10835 : 533580 : + if (xid_count > 0) 10836 : : + { 10837 : 28331 : + mark_xids_active(binlog_id, xid_count); 10838 : : + } 10839 : : 10840 : 533580 : + if (rotate(false, &check_purge)) 10841 : : + { 10842 : : + /* 10843 : : + If we fail to rotate, which thread should get the error? 10844 : : + We give the error to the leader, as any my_error() thrown inside 10845 : : + rotate() will have been registered for the leader THD. 10846 : : 10847 : : + However we must not return error from here - that would cause 10848 : : + ha_commit_trans() to abort and rollback the transaction, which would 10849 : : + leave an inconsistent state with the transaction committed in the 10850 : : + binlog but rolled back in the engine. 10851 : : + 10852 : : + Instead set a flag so that we can return error later, from unlog(), 10853 : : + when the transaction has been safely committed in the engine. 10854 : : + */ 10855 : 96 : + leader->cache_mngr->delayed_error= true; 10856 : 96 : + my_error(ER_ERROR_ON_WRITE, MYF(ME_ERROR_LOG), name, errno); 10857 : 96 : + check_purge= false; 10858 : : + } 10859 : : + /* In case of binlog rotate, update the correct current binlog offset. */ 10860 : 533574 : + commit_offset= my_b_write_tell(&log_file); 10861 : : } 10862 : : } 10863 : : 10864 : 664637 : DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_after_binlog_sync"); 10873 : : 10874 : 664637 : DEBUG_SYNC(leader->thd, "commit_after_release_LOCK_log"); 10875 : : 10876 : : +#ifdef HAVE_REPLICATION 10877 : : /* 10878 : : Loop through threads and run the binlog_sync hook 10879 : : + AFTER_SYNC is not available for --binlog-in-engine, as there we avoid the 10880 : : + costly two-phase commit between binlog and engine. 10881 : : */ 10882 : 1198211 : + if (!opt_binlog_engine_hton && 10883 : 533574 : + unlikely(repl_semisync_master.get_master_enabled())) 10884 : : { 10885 : 9298 : mysql_mutex_assert_not_owner(&LOCK_prepare_ordered); 10886 : 9298 : mysql_mutex_assert_not_owner(&LOCK_log); 10892 : 18736 : for (current= leader; current != NULL; current= current->next) 10893 : : { 10894 : 9438 : last= current->next == NULL; 10895 : 9438 : if (likely(!current->error)) 10896 : 9438 : current->error= 10897 : 9438 : repl_semisync_master.wait_after_sync(current->cache_mngr-> 10898 : 9438 : + last_commit_pos_file.legacy_name, 10899 : 9438 : + (my_off_t)current->cache_mngr-> 10900 : 9438 : last_commit_pos_offset); 10901 : 9438 : first= false; 10902 : : } 10903 : : } 10904 : : +#endif 10905 : : 10906 : 664637 : DEBUG_SYNC(leader->thd, "commit_before_get_LOCK_commit_ordered"); 10907 : : 10910 : : { 10911 : : DBUG_SUICIDE(); 10912 : : }); 10913 : 664637 : + if (opt_binlog_engine_hton) 10914 : 131063 : + tail->do_binlog_group_commit_ordered= true; 10915 : : + else 10916 : 533574 : + last_commit_pos_offset= commit_offset; 10917 : : 10918 : : /* 10919 : : Unlock LOCK_after_binlog_sync only *after* LOCK_commit_ordered has been 10959 : 1335114 : while (current != NULL) 10960 : : { 10961 : : group_commit_entry *next; 10962 : 670529 : + binlog_cache_mngr *cache_mngr= current->cache_mngr; 10963 : : 10964 : 670529 : DEBUG_SYNC(leader->thd, "commit_loop_entry_commit_ordered"); 10965 : 670527 : + cache_mngr->engine_binlogged= FALSE; 10966 : 670527 : ++num_commits; 10967 : 670527 : set_current_thd(current->thd); 10968 : 1063016 : + if (cache_mngr->using_xa && likely(!current->error) && 10969 : 392489 : !DBUG_IF("skip_commit_ordered")) 10970 : 392489 : run_commit_ordered(current->thd, current->all); 10971 : : + 10972 : 670525 : + if (unlikely(!cache_mngr->engine_binlogged) && opt_binlog_engine_hton) 10973 : : + { 10974 : : + /* 10975 : : + If the binlog engine did not binlog for us as part of its own internal 10976 : : + transaction commit during commit_ordered(), we need to binlog it 10977 : : + explicitly here, while still holding LOCK_commit_ordered to ensure the 10978 : : + correct commit order. 10979 : : + 10980 : : + The common case is a normal transaction in the binlog engine, and we 10981 : : + will not hit this condition. But it can happen for example when mixing 10982 : : + transactional and non-transactional DML in the same event group, or when 10983 : : + doing CREATE TABLE ... SELECT using row-based binlogging. 10984 : : + */ 10985 : 588 : + binlog_cache_data *cache_data= cache_mngr->engine_cache_data(); 10986 : 588 : + IO_CACHE *file= &cache_data->cache_log; 10987 : 588 : + handler_binlog_event_group_info *engine_context= 10988 : : + &cache_data->engine_binlog_info; 10989 : 588 : + binlog_setup_engine_commit_data(engine_context, cache_mngr); 10990 : 588 : + if (likely(!current->error)) 10991 : : + { 10992 : 1176 : + current->error= (*opt_binlog_engine_hton->binlog_write_direct_ordered) 10993 : 588 : + (file, engine_context, current->thd->get_last_commit_gtid()); 10994 : 588 : + if (likely(!current->error)) 10995 : : + { 10996 : 588 : + cache_mngr->last_commit_pos_file.engine_file_no= 10997 : 588 : + engine_context->out_file_no; 10998 : 588 : + cache_mngr->last_commit_pos_offset= engine_context->out_file_no; 10999 : : + 11000 : : + /* Mark to call binlog_write_direct later. */ 11001 : 588 : + cache_mngr->need_write_direct= TRUE; 11002 : : + } 11003 : : + } 11004 : : + } 11005 : : + 11006 : 670525 : current->thd->wakeup_subsequent_commits(current->error); 11007 : : 11008 : : /* 11024 : 664585 : mysql_mutex_unlock(&LOCK_commit_ordered); 11025 : 664585 : DEBUG_SYNC(leader->thd, "commit_after_group_release_commit_ordered"); 11026 : : 11027 : 664573 : + if (check_purge && !opt_binlog_engine_hton) 11028 : 334 : checkpoint_and_purge(binlog_id); 11029 : : 11030 : 664573 : DBUG_VOID_RETURN; 11050 : : An error in the trx_cache will truncate the cache to the last good 11051 : : statement, it won't leave a lingering error. Assert that this holds. 11052 : : */ 11053 : 670435 : + DBUG_ASSERT(!(mngr->using_trx_cache && !mngr->trx_cache.empty() && 11054 : : mngr->get_binlog_cache_log(TRUE)->error)); 11055 : : /* 11056 : : An error in the stmt_cache would be caught on the higher level and result 11057 : : in an incident event being written over a (possibly corrupt) cache content. 11058 : : Assert that this holds. 11059 : : */ 11060 : 670435 : + DBUG_ASSERT(!(mngr->using_stmt_cache && !mngr->stmt_cache.empty() && 11061 : : mngr->get_binlog_cache_log(FALSE)->error)); 11062 : : 11063 : 670435 : + if (!opt_binlog_engine_hton) 11064 : : + { 11065 : : /* 11066 : : gtid will be written when renaming the binlog cache to binlog file, 11067 : : if commit_by_rotate is true. Thus skip write_gtid_event here. 11068 : : */ 11069 : 535854 : if (likely(!commit_by_rotate)) 11070 : : { 11071 : 535832 : + if (write_gtid_event(entry->thd, nullptr, is_prepared_xa(entry->thd), 11072 : 535832 : + mngr->using_trx_cache, commit_id, 11073 : 535832 : false /* commit_by_rotate */, has_xid, entry->ro_1pc)) 11074 : 18 : DBUG_RETURN(ER_ERROR_ON_WRITE); 11075 : : } 11076 : : 11077 : 663897 : + if (mngr->using_stmt_cache && !mngr->stmt_cache.empty() && 11078 : 128061 : write_cache(entry->thd, mngr->get_binlog_cache_data(FALSE))) 11079 : : { 11080 : 0 : entry->error_cache= &mngr->stmt_cache.cache_log; 11081 : 0 : DBUG_RETURN(ER_ERROR_ON_WRITE); 11082 : : } 11083 : : 11084 : 535836 : + if (mngr->using_trx_cache && !mngr->trx_cache.empty()) 11085 : : { 11086 : 379971 : DBUG_EXECUTE_IF("crash_before_writing_xid", 11087 : : { 11109 : : DBUG_RETURN(ER_ERROR_ON_WRITE); 11110 : : }); 11111 : : 11112 : : + /* 11113 : : + Write the end event (XID_EVENT, commit QUERY_LOG_EVENT) directly to the 11114 : : + legacy binary log. This is required to get the correct end position in the 11115 : : + event as currently needed by non-GTID slaves (since write_cache() does a 11116 : : + direct write of the cache, leaving end positions at zero). 11117 : : + */ 11118 : 535782 : if (write_event(entry->end_event)) 11119 : : { 11120 : 0 : entry->error_cache= NULL; 11122 : : } 11123 : 535782 : status_var_add(entry->thd->status_var.binlog_bytes_written, 11124 : : entry->end_event->data_written); 11125 : : + } 11126 : : + else 11127 : : + { 11128 : 134581 : + DBUG_ASSERT((mngr->using_stmt_cache && !mngr->stmt_cache.empty()) || 11129 : : + (mngr->using_trx_cache && !mngr->trx_cache.empty()) || 11130 : : + (mngr->using_trx_cache && 11131 : : + mngr->trx_cache.engine_binlog_info.xa_xid != nullptr) 11132 : : + /* 11133 : : + Assert that empty transaction is handled elsewhere. 11134 : : + Except in XA COMMIT, all events are OOB-spilled with the 11135 : : + prepare record, the caches are empty. 11136 : : + */ 11137 : : + ); 11138 : 134581 : + if (unlikely((mngr->using_stmt_cache && !mngr->stmt_cache.empty()) && 11139 : : + (mngr->using_trx_cache && !mngr->trx_cache.empty()))) 11140 : : + { 11141 : : + /* 11142 : : + We have data in both the statement and the transaction cache. 11143 : : + This is usually not the case, but it can occur in autocommit when 11144 : : + both transactional and non-transactional tables are changed in a 11145 : : + single statement, for example multi-table update or implicit 11146 : : + allocation from SEQUENCE as part of transactional INSERT. 11147 : : + 11148 : : + We do not want the complexity of handling two different caches in 11149 : : + the engine binlog API for this uncommon case, so we spill both 11150 : : + caches as OOB data here, leaving just the transaction cache 11151 : : + containing the GTID event. 11152 : : + 11153 : : + (We still need to handle dual OOB data streams, as we might need to 11154 : : + roll back the transaction one and binlog the statement one). 11155 : : + */ 11156 : 20 : + if (my_b_flush_io_cache(&mngr->stmt_cache.cache_log, 0) || 11157 : 10 : + my_b_flush_io_cache(&mngr->trx_cache.cache_log, 0)) 11158 : 0 : + DBUG_RETURN(ER_ERROR_ON_WRITE); 11159 : : + } 11160 : : + 11161 : 134581 : + binlog_cache_data *cache_data= mngr->engine_cache_data(); 11162 : : + /* 11163 : : + The GTID event cannot go first since we only allocate the GTID at binlog 11164 : : + time. So write the GTID at the very end, and record its offset so that the 11165 : : + engine can pick it out and binlog it at the start. 11166 : : + */ 11167 : 134581 : + cache_data->engine_binlog_info.gtid_offset= my_b_tell(&cache_data->cache_log); 11168 : 134581 : + if (write_gtid_event(entry->thd, cache_data, false, 11169 : 134581 : + mngr->using_trx_cache, commit_id, false, 11170 : 134581 : + has_xid, entry->ro_1pc)) 11171 : 0 : + DBUG_RETURN(ER_ERROR_ON_WRITE); 11172 : : + } 11173 : : 11174 : 670363 : if (entry->incident_event) 11175 : : { 11373 : 256790 : int ret= 0; 11374 : 256790 : DBUG_ENTER("wait_for_update_binlog_end_pos"); 11375 : : 11376 : 256790 : + DBUG_ASSERT(!opt_binlog_engine_hton); 11377 : 256790 : thd_wait_begin(thd, THD_WAIT_BINLOG); 11378 : 256790 : mysql_mutex_assert_owner(get_binlog_end_pos_lock()); 11379 : 256790 : if (!timeout) 11518 : 260172 : } 11519 : : 11520 : : 11521 : : +void 11522 : 161 : +MYSQL_BIN_LOG::close_engine() 11523 : : +{ 11524 : 161 : + log_state= LOG_CLOSED; 11525 : 161 : +} 11526 : : + 11527 : : + 11528 : : /* 11529 : : Clear the LOG_EVENT_BINLOG_IN_USE_F; this marks the binlog file as cleanly 11530 : : closed and not needing crash recovery. 11550 : 264 : mysql_mutex_lock(&LOCK_log); 11551 : 264 : if (is_open()) 11552 : 158 : max_size= max_size_arg; 11553 : 264 : + if (opt_binlog_engine_hton) 11554 : : + { 11555 : 20 : + (*opt_binlog_engine_hton->set_binlog_max_size)((size_t)max_size_arg); 11556 : 20 : + binlog_max_spill_size= 11557 : 20 : + std::min((size_t)(max_size_arg / 2), BINLOG_SPILL_MAX); 11558 : : + } 11559 : 264 : mysql_mutex_unlock(&LOCK_log); 11560 : 528 : DBUG_VOID_RETURN; 11561 : 264 : } 12443 : 0 : ++pending->pending_count; 12444 : 0 : } 12445 : : 12446 : 7 : +int TC_LOG_MMAP::unlog(THD *thd, ulong cookie, my_xid xid) 12447 : : { 12448 : 7 : pending_cookies *full_buffer= NULL; 12449 : 7 : uint32 ncookies= tc_log_page_size / sizeof(my_xid); 12806 : : #endif 12807 : 10 : return error > 0; 12808 : : } 12809 : : + 12810 : : + 12811 : : +static void 12812 : 92 : +binlog_recover_hash_free(void *p) 12813 : : +{ 12814 : 92 : + const handler_binlog_xid_info *info= 12815 : : + reinterpret_cast(p); 12816 : 92 : + delete info; 12817 : 92 : +} 12818 : : + 12819 : : + 12820 : : +static const uchar * 12821 : 362 : +binlog_recover_hash_key(const void *p, size_t *out_len, my_bool) 12822 : : +{ 12823 : 362 : + const XID *xid= &(reinterpret_cast(p)->xid); 12824 : 362 : + *out_len= xid->key_length(); 12825 : 362 : + return xid->key(); 12826 : : +} 12827 : : + 12828 : : + 12829 : 7788 : int TC_LOG_BINLOG::open(const char *opt_name) 12830 : : { 12831 : 7788 : int error= 1; 12835 : 7788 : DBUG_ASSERT(opt_name); 12836 : 7788 : DBUG_ASSERT(opt_name[0]); 12837 : : 12838 : 7788 : + if (opt_binlog_engine_hton) 12839 : : + { 12840 : : + HASH recover_hash; 12841 : 169 : + my_hash_init(key_memory_binlog_recover_exec, &recover_hash, &my_charset_bin, 12842 : : + 128, 0, sizeof(XID), binlog_recover_hash_key, 12843 : : + binlog_recover_hash_free, MYF(HASH_UNIQUE)); 12844 : 338 : + if ((*opt_binlog_engine_hton->binlog_init) 12845 : 169 : + ((size_t)max_binlog_size, opt_binlog_directory, &recover_hash)) 12846 : : + { 12847 : 0 : + my_hash_free(&recover_hash); 12848 : 0 : + DBUG_RETURN(1); 12849 : : + } 12850 : : + 12851 : 169 : + bool err= ha_recover_engine_binlog(&recover_hash); 12852 : 169 : + my_hash_free(&recover_hash); 12853 : 169 : + if (err) 12854 : 0 : + DBUG_RETURN(1); 12855 : : + 12856 : : + /* Engine binlog implementation recovers the GTID state by itself. */ 12857 : 169 : + binlog_state_recover_done= true; 12858 : 169 : + DBUG_RETURN(0); 12859 : : + } 12860 : : + 12861 : 7619 : if (!my_b_inited(&index_file)) 12862 : : { 12863 : : /* There was a failure to open the index file, can't open the binlog */ 12910 : : 12911 : 394990 : DEBUG_SYNC(thd, "binlog_after_log_and_order"); 12912 : : 12913 : 394985 : bool need_unlog= cache_mngr->need_unlog; 12914 : 394985 : + bool need_engine_2pc= cache_mngr->need_engine_2pc; 12915 : : /* 12916 : : The transaction won't need the flag anymore. 12917 : : Todo/fixme: consider to move the statement into cache_mngr->reset() 12918 : : relocated to the current or later point. 12919 : : */ 12920 : 394985 : cache_mngr->need_unlog= false; 12921 : 394985 : + cache_mngr->need_engine_2pc= false; 12922 : : + 12923 : 394985 : + if (err) 12924 : 2464 : + DBUG_RETURN(0); 12925 : : + 12926 : 392521 : + if (unlikely(need_engine_2pc)) 12927 : : + { 12928 : 40 : + DBUG_ASSERT(!need_unlog); 12929 : 40 : + DBUG_RETURN(BINLOG_COOKIE_ENGINE_UNLOG(cache_mngr->delayed_error)); 12930 : : + } 12931 : : /* 12932 : : If using explicit user XA, we will not have XID. We must still return a 12933 : : non-zero cookie (as zero cookie signals error). 13088 : 8139 : DBUG_VOID_RETURN; 13089 : 60632 : } 13090 : : 13091 : 420430 : +int TC_LOG_BINLOG::unlog(THD *thd, ulong cookie, my_xid xid) 13092 : : { 13093 : 420430 : DBUG_ENTER("TC_LOG_BINLOG::unlog"); 13094 : 420423 : if (!xid) 13095 : 0 : DBUG_RETURN(0); 13096 : : 13097 : 420423 : + if (BINLOG_COOKIE_IS_ENGINE_UNLOG(cookie)) 13098 : : + { 13099 : 39 : + DBUG_ASSERT(opt_binlog_engine_hton); 13100 : 39 : + binlog_cache_mngr *cache_mngr= thd->binlog_get_cache_mngr(); 13101 : 39 : + DBUG_ASSERT(cache_mngr != nullptr); 13102 : 39 : + if (likely(cache_mngr != nullptr)) 13103 : : + { 13104 : 39 : + cache_mngr->xid_buf.set(xid); 13105 : 39 : + (*opt_binlog_engine_hton->binlog_unlog) 13106 : 39 : + (&cache_mngr->xid_buf, 13107 : : + &cache_mngr->trx_cache.engine_binlog_info.engine_ptr); 13108 : : + } 13109 : : + } 13110 : 420384 : + else if (!BINLOG_COOKIE_IS_DUMMY(cookie)) 13111 : 28327 : mark_xid_done(BINLOG_COOKIE_GET_ID(cookie), true); 13112 : : /* 13113 : : See comment in trx_group_commit_with_engines() - if rotate() gave a failure, 13125 : : { 13126 : 32367 : DBUG_ASSERT(is_preparing_xa(thd)); 13127 : : 13128 : 32367 : + if (opt_binlog_engine_hton) 13129 : 54 : + return 0; 13130 : : + 13131 : 32313 : binlog_cache_mngr *cache_mngr= thd->binlog_setup_trx_data(); 13132 : 32313 : int cookie= 0; 13133 : : 13158 : 27987 : cookie= BINLOG_COOKIE_MAKE(cache_mngr->binlog_id, cache_mngr->delayed_error); 13159 : 27987 : cache_mngr->need_unlog= false; 13160 : : 13161 : 27987 : + return unlog(thd, cookie, 1); 13162 : : } 13163 : : 13164 : : 14575 : 873173 : mysql_bin_log_commit_pos(THD *thd, ulonglong *out_pos, const char **out_file) 14576 : : { 14577 : : binlog_cache_mngr *cache_mngr; 14578 : 1231541 : + if (likely(opt_bin_log) && likely(!opt_binlog_engine_hton) && 14579 : 358368 : (cache_mngr= thd->binlog_get_cache_mngr())) 14580 : : { 14581 : 334096 : + *out_file= cache_mngr->last_commit_pos_file.legacy_name; 14582 : 334096 : *out_pos= (ulonglong)(cache_mngr->last_commit_pos_offset); 14583 : : } 14584 : : else 14599 : 288 : ulong UNINIT_VAR(prev_binlog_id); 14600 : : 14601 : 288 : mysql_mutex_unlock(&LOCK_global_system_variables); 14602 : 288 : + if (opt_binlog_engine_hton && value) 14603 : : + { 14604 : 0 : + sql_print_information("Value of binlog_checksum forced to NONE since " 14605 : : + "binlog_storage_engine is enabled, where " 14606 : : + "per-event checksumming is not needed"); 14607 : 0 : + value= 0; 14608 : : + } 14609 : 288 : mysql_mutex_lock(mysql_bin_log.get_log_lock()); 14610 : 288 : if(mysql_bin_log.is_open()) 14611 : : { 14702 : : { 14703 : 25985 : mysql_mutex_lock(&thd->LOCK_thd_data); 14704 : 25985 : auto cache_mngr= thd->binlog_get_cache_mngr(); 14705 : 25985 : + if (cache_mngr) 14706 : : { 14707 : 5422 : + if (opt_binlog_engine_hton) 14708 : : + { 14709 : 106 : + have_snapshot= 14710 : 106 : + cache_mngr->last_commit_pos_file.engine_file_no != ~(uint64_t)0; 14711 : 106 : + if (have_snapshot) 14712 : : + { 14713 : : + char buf[FN_REFLEN]; 14714 : 4 : + uint64_t file_no= cache_mngr->last_commit_pos_file.engine_file_no; 14715 : 4 : + (*opt_binlog_engine_hton->get_filename)(buf, file_no); 14716 : 4 : + set_binlog_snapshot_file(buf); 14717 : 4 : + binlog_snapshot_position= cache_mngr->last_commit_pos_offset; 14718 : : + } 14719 : : + } 14720 : : + else 14721 : : + { 14722 : 5316 : + have_snapshot= cache_mngr->last_commit_pos_file.legacy_name[0] != '\0'; 14723 : 5316 : + if (have_snapshot) 14724 : : + { 14725 : 30 : + set_binlog_snapshot_file(cache_mngr->last_commit_pos_file.legacy_name); 14726 : 30 : + binlog_snapshot_position= cache_mngr->last_commit_pos_offset; 14727 : : + } 14728 : : + } 14729 : : } 14730 : 25985 : mysql_mutex_unlock(&thd->LOCK_thd_data); 14731 : : } 14735 : 79000 : binlog_status_var_num_group_commits= this->num_group_commits; 14736 : 79000 : if (!have_snapshot) 14737 : : { 14738 : 78966 : + if (opt_binlog_engine_hton) 14739 : : + { 14740 : : + char buf[FN_REFLEN]; 14741 : : + uint64_t file_no; 14742 : : + uint64_t offset; 14743 : 518 : + (*opt_binlog_engine_hton->binlog_status)(&file_no, &offset); 14744 : 518 : + (*opt_binlog_engine_hton->get_filename)(buf, file_no); 14745 : 518 : + set_binlog_snapshot_file(buf); 14746 : 518 : + binlog_snapshot_position= (ulonglong)offset; 14747 : : + } 14748 : : + else 14749 : : + { 14750 : 78448 : + set_binlog_snapshot_file(last_commit_pos_file); 14751 : 78448 : + binlog_snapshot_position= last_commit_pos_offset; 14752 : : + } 14753 : : } 14754 : 79000 : mysql_mutex_unlock(&LOCK_commit_ordered); 14755 : 79000 : mysql_mutex_lock(&LOCK_prepare_ordered); 14939 : : Set an implicit savepoint in order to be able to truncate a trx-cache. 14940 : : */ 14941 : 14 : my_off_t pos= 0; 14942 : 14 : + binlog_trans_log_savepos(thd, cache_mngr, &pos); 14943 : 14 : cache_mngr->trx_cache.set_prev_position(pos); 14944 : : 14945 : : /* 14962 : 673412 : inline bool Binlog_commit_by_rotate::should_commit_by_rotate( 14963 : : const MYSQL_BIN_LOG::group_commit_entry *entry) const 14964 : : { 14965 : 673412 : + binlog_cache_mngr *mngr= entry->cache_mngr; 14966 : 673412 : + binlog_cache_data *trx_cache= mngr->get_binlog_cache_data(true); 14967 : 673417 : + binlog_cache_data *stmt_cache= mngr->get_binlog_cache_data(false); 14968 : : 14969 : 673415 : if (likely(trx_cache->get_byte_position() <= 14970 : : opt_binlog_commit_by_rotate_threshold && 14973 : 673378 : return false; 14974 : : 14975 : 44 : binlog_cache_data *cache_data= trx_cache; 14976 : 44 : + if (unlikely(mngr->using_stmt_cache && !stmt_cache->empty())) 14977 : 8 : cache_data= stmt_cache; 14978 : : 14979 : : /* 14994 : : enabled, skip the disabled case to make the code simple. 14995 : : */ 14996 : 80 : if (encrypt_binlog || !opt_optimize_thread_scheduling || 14997 : 40 : + (mngr->using_stmt_cache && mngr->using_trx_cache && 14998 : 28 : !stmt_cache->empty() && !trx_cache->empty())) 14999 : 2 : return false; 15000 : : 15007 : 38 : THD *thd= entry->thd; 15008 : 38 : binlog_cache_mngr *cache_mngr= entry->cache_mngr; 15009 : 38 : binlog_cache_data *cache_data= cache_mngr->get_binlog_cache_data(true); 15010 : 38 : + if (unlikely(!cache_mngr->using_trx_cache || cache_data->empty())) 15011 : 6 : cache_data= cache_mngr->get_binlog_cache_data(false); 15012 : : 15013 : : /* Call them before enter log_lock to avoid holding the lock long */ 15102 : 30 : size_t binlog_size= my_b_tell(&mysql_bin_log.log_file); 15103 : 30 : size_t required_size= binlog_size; 15104 : : // space for Gtid_log_event 15105 : 30 : + required_size+= LOG_EVENT_HEADER_LEN + Gtid_log_event::max_size + 15106 : : BINLOG_CHECKSUM_LEN; 15107 : : 15108 : 30 : DBUG_EXECUTE_IF("simulate_required_size_too_big", required_size= 10000;); 15163 : 28 : ret= true; 15164 : : 15165 : 28 : if (mysql_bin_log.write_gtid_event( 15166 : 28 : + m_entry->thd, nullptr, is_prepared_xa(m_entry->thd), 15167 : 28 : + m_entry->cache_mngr->using_trx_cache, 15168 : : 0 /* commit_id */, true /* commit_by_rotate */, 15169 : 28 : m_entry->end_event->get_type_code() == XID_EVENT, m_entry->ro_1pc)) 15170 : 0 : goto err; ===== File: sql/log.h ===== 28 : : 29 : : bool reopen_fstreams(const char *filename, FILE *outstream, FILE *errstream); 30 : : void setup_log_handling(); 31 : : +void give_purge_note(const char *reason, const char *file_name, 32 : : + bool interactive); 33 : : bool trans_has_updated_trans_table(const THD* thd); 34 : : bool stmt_has_updated_trans_table(const THD *thd); 35 : : bool use_trans_cache(const THD* thd, bool is_transactional); 63 : : virtual int log_and_order(THD *thd, my_xid xid, bool all, 64 : : bool need_prepare_ordered, 65 : : bool need_commit_ordered) = 0; 66 : : + virtual int unlog(THD *thd, ulong cookie, my_xid xid)=0; 67 : : + virtual int log_xa_prepare(THD *thd, bool all)= 0; 68 : : virtual int unlog_xa_prepare(THD *thd, bool all)= 0; 69 : : virtual void commit_checkpoint_notify(void *cookie)= 0; 70 : : 119 : 0 : DBUG_ASSERT(0); 120 : 0 : return 1; 121 : : } 122 : 0 : + int unlog(THD *thd, ulong cookie, my_xid xid) override { return 0; } 123 : 0 : + int log_xa_prepare(THD *thd, bool all) override 124 : : + { 125 : 0 : + return 0; 126 : : + } 127 : 0 : int unlog_xa_prepare(THD *thd, bool all) override 128 : : { 129 : 0 : return 0; 210 : : void close() override; 211 : : int log_and_order(THD *thd, my_xid xid, bool all, 212 : : bool need_prepare_ordered, bool need_commit_ordered) override; 213 : : + int unlog(THD *thd, ulong cookie, my_xid xid) override; 214 : 216 : + int log_xa_prepare(THD *thd, bool all) override 215 : : + { 216 : 216 : + return 0; 217 : : + } 218 : 216 : int unlog_xa_prepare(THD *thd, bool all) override 219 : : { 220 : 216 : return 0; 277 : : */ 278 : : typedef struct st_log_info 279 : : { 280 : : + /* file_no only used when --binlog-storage-engine set. */ 281 : : + std::atomic file_no; 282 : : + /* log_file_name and *_offset only used when --binlog-storage-engine unset. */ 283 : : char log_file_name[FN_REFLEN]; 284 : : my_off_t index_file_offset, index_file_start_offset; 285 : : my_off_t pos; 286 : 685806 : + st_log_info() : file_no(~(uint64_t)0), index_file_offset(0), 287 : 685808 : + index_file_start_offset(0), pos(0) 288 : : { 289 : 685808 : DBUG_ENTER("LOG_INFO"); 290 : 685804 : log_file_name[0] = '\0'; 590 : : case where there is no corresponding binlog id (since nothing was logged). 591 : : And we need an error flag to mark that unlog() must return failure. 592 : : 593 : : + For --binlog-storage-engine, we need unlog if another engine than the 594 : : + binlog engine participated in the transaction, or if we did a user XA 595 : : + commit. 596 : : + 597 : : We use the following macros to pack all of this information into the single 598 : : ulong available with log_and_order() / unlog(). 599 : : 602 : : */ 603 : : #define BINLOG_COOKIE_ERROR_RETURN 0 604 : : #define BINLOG_COOKIE_DUMMY_ID 1 605 : : +#define BINLOG_COOKIE_ENGINE_UNLOG_ID 2 606 : : +#define BINLOG_COOKIE_BASE 3 607 : : #define BINLOG_COOKIE_DUMMY(error_flag) \ 608 : : ( (BINLOG_COOKIE_DUMMY_ID<<1) | ((error_flag)&1) ) 609 : : +#define BINLOG_COOKIE_ENGINE_UNLOG(error_flag) \ 610 : : + ( (BINLOG_COOKIE_ENGINE_UNLOG_ID<<1) | ((error_flag)&1) ) 611 : : #define BINLOG_COOKIE_MAKE(id, error_flag) \ 612 : : ( (((id)+BINLOG_COOKIE_BASE)<<1) | ((error_flag)&1) ) 613 : : #define BINLOG_COOKIE_GET_ERROR_FLAG(c) ((c) & 1) 614 : : #define BINLOG_COOKIE_GET_ID(c) ( ((ulong)(c)>>1) - BINLOG_COOKIE_BASE ) 615 : : #define BINLOG_COOKIE_IS_DUMMY(c) \ 616 : : ( ((ulong)(c)>>1) == BINLOG_COOKIE_DUMMY_ID ) 617 : : +#define BINLOG_COOKIE_IS_ENGINE_UNLOG(c) \ 618 : : + ( ((ulong)(c)>>1) == BINLOG_COOKIE_ENGINE_UNLOG_ID ) 619 : : 620 : : 621 : : class binlog_cache_mngr; 623 : : struct rpl_gtid; 624 : : struct wait_for_commit; 625 : : class Binlog_commit_by_rotate; 626 : : +struct rpl_binlog_state_base; 627 : : 628 : : class MYSQL_BIN_LOG: public TC_LOG, public Event_log 629 : : { 656 : : struct group_commit_entry *next; 657 : : THD *thd; 658 : : binlog_cache_mngr *cache_mngr; 659 : : /* 660 : : Extra events (COMMIT/ROLLBACK/XID, and possibly INCIDENT) to be 661 : : written during group commit. The incident_event is only valid if 667 : : int error; 668 : : int commit_errno; 669 : : IO_CACHE *error_cache; 670 : : + ulong binlog_id; 671 : : /* This is the `all' parameter for ha_commit_ordered(). */ 672 : : bool all; 673 : : /* 684 : : bool check_purge; 685 : : /* Flag used to optimise around wait_for_prior_commit. */ 686 : : bool queued_by_other; 687 : : bool ro_1pc; // passes the binlog_cache_mngr::ro_1pc value to Gtid ctor 688 : : + /* 689 : : + Set for the last participant in group commit, it must invoke 690 : : + binlog_group_commit_ordered (in case of --binlog-storage-engine) after 691 : : + LOCK_commit_ordered has been released. 692 : : + */ 693 : : + bool do_binlog_group_commit_ordered; 694 : : }; 695 : : 696 : : /* 704 : : uint reset_master_pending; 705 : : ulong mark_xid_done_waiting; 706 : : 707 : : + /* 708 : : + Protect against binlog readers (eg. slave dump threads) running 709 : : + concurrently with RESET MASTER. 710 : : + binlog_use_count counts the number of active readers, or is -1 when a 711 : : + RESET MASTER is running. It is protected by LOCK_binlog_use and 712 : : + COND_binlog_use is signalled when RESET MASTER completes so new 713 : : + readers can wait for that. 714 : : + */ 715 : : + int32_t binlog_use_count; 716 : : + mysql_mutex_t LOCK_binlog_use; 717 : : + mysql_cond_t COND_binlog_use; 718 : : + 719 : : /* LOCK_log and LOCK_index are inited by init_pthread_objects() */ 720 : : mysql_mutex_t LOCK_index; 721 : : mysql_mutex_t LOCK_xid_list; 811 : : group_commit_entry *tail, 812 : : bool commit_by_rotate); 813 : : bool is_xidlist_idle_nolock(); 814 : : + void update_gtid_index(uint32 offset, const rpl_gtid *gtid); 815 : : 816 : : public: 817 : : void purge(bool all); 959 : : ulong next_log_number) override; 960 : : int log_and_order(THD *thd, my_xid xid, bool all, 961 : : bool need_prepare_ordered, bool need_commit_ordered) override; 962 : : + int unlog(THD *thd, ulong cookie, my_xid xid) override; 963 : : + int log_xa_prepare(THD *thd, bool all) override; 964 : : int unlog_xa_prepare(THD *thd, bool all) override; 965 : : void commit_checkpoint_notify(void *cookie) override; 966 : : int recover(LOG_INFO *linfo, const char *last_log_name, IO_CACHE *first_log, 1012 : 2125561 : signal_relay_log_update(); 1013 : : else 1014 : : { 1015 : 13159 : + DBUG_ASSERT(!opt_binlog_engine_hton); 1016 : 13159 : lock_binlog_end_pos(); 1017 : 13160 : binlog_end_pos= my_b_safe_tell(&log_file); 1018 : 13160 : signal_bin_log_update(); 1021 : 2138643 : } 1022 : 730829 : void update_binlog_end_pos(my_off_t pos) 1023 : : { 1024 : 730829 : + DBUG_ASSERT(!opt_binlog_engine_hton); 1025 : 730829 : mysql_mutex_assert_owner(&LOCK_log); 1026 : 730829 : mysql_mutex_assert_not_owner(&LOCK_binlog_end_pos); 1027 : 730829 : lock_binlog_end_pos(); 1050 : : bool null_created, 1051 : : bool need_mutex, 1052 : : bool commit_by_rotate = false); 1053 : : + bool open_engine(handlerton *hton, ulong max_size, const char *dir); 1054 : : bool open_index_file(const char *index_file_name_arg, 1055 : : const char *log_name, bool need_mutex); 1056 : : /* Use this to start writing a new log file */ 1094 : : bool commit_by_rotate= false); 1095 : : void checkpoint_and_purge(ulong binlog_id); 1096 : : int rotate_and_purge(bool force_rotate, DYNAMIC_ARRAY* drop_gtid_domain= NULL); 1097 : : + int flush_binlogs_engine(DYNAMIC_ARRAY *domain_drop_lex); 1098 : 8039 : + int flush_binlog(DYNAMIC_ARRAY* drop_gtid_domain) 1099 : : + { 1100 : 8039 : + if (opt_binlog_engine_hton) 1101 : 432 : + return flush_binlogs_engine(drop_gtid_domain); 1102 : : + else 1103 : 7607 : + return rotate_and_purge(true, drop_gtid_domain); 1104 : : + } 1105 : : /** 1106 : : Flush binlog cache and synchronize to disk. 1107 : : 1137 : 729939 : return 0; 1138 : 84 : return real_purge_logs_by_size(binlog_pos); 1139 : : } 1140 : : + void engine_purge_logs_by_size(ulonglong max_total_size); 1141 : : int set_purge_index_file_name(const char *base_file_name); 1142 : : int open_purge_index_file(bool destroy); 1143 : : bool truncate_and_remove_binlogs(const char *truncate_file, 1151 : : int register_create_index_entry(const char* entry); 1152 : : int purge_index_entry(THD *thd, ulonglong *decrease_log_space, 1153 : : bool need_mutex); 1154 : : + bool start_use_binlog(THD *thd); 1155 : : + void end_use_binlog(THD *thd); 1156 : : bool reset_logs(THD* thd, bool create_new_log, 1157 : : rpl_gtid *init_state, uint32 init_state_len, 1158 : : ulong next_log_number); 1159 : : + bool reset_engine_binlogs(THD *thd, rpl_gtid *init_state, 1160 : : + uint32 init_state_len); 1161 : : void wait_for_last_checkpoint_event(); 1162 : : void close(uint exiting); 1163 : : + void close_engine(); 1164 : : void clear_inuse_flag_when_closing(File file); 1165 : : 1166 : : // iterating through the log index file 1183 : 2064338 : inline uint32 get_open_count() { return open_count; } 1184 : : void set_status_variables(THD *thd); 1185 : : bool is_xidlist_idle(); 1186 : : + bool write_gtid_event(THD *thd, binlog_cache_data *cache_data, 1187 : : + bool standalone, bool is_transactional, 1188 : : + uint64 commit_id, bool commit_by_rotate, 1189 : : + bool has_xid, bool ro_1pc); 1190 : : int read_state_from_file(); 1191 : : int write_state_to_file(); 1192 : : int get_most_recent_gtid_list(rpl_gtid **list, uint32 *size); 1259 : : char binlog_end_pos_file[FN_REFLEN]; 1260 : : }; 1261 : : 1262 : : +extern bool load_global_binlog_state(rpl_binlog_state_base *state); 1263 : : +extern bool binlog_recover_gtid_state(rpl_binlog_state_base *state, 1264 : : + handler_binlog_reader *reader); 1265 : : + 1266 : : + 1267 : : class Log_event_handler 1268 : : { 1269 : : public: 1568 : : get_gtid_list_event(IO_CACHE *cache, Gtid_list_log_event **out_gtid_list); 1569 : : 1570 : : int binlog_commit(THD *thd, bool all, bool is_ro_1pc= false); 1571 : : +void binlog_post_commit(THD *thd, bool all); 1572 : : +void binlog_post_rollback(THD *thd, bool all); 1573 : : int binlog_commit_by_xid(XID *xid); 1574 : : int binlog_rollback_by_xid(XID *xid); 1575 : : +void binlog_post_commit_by_xid(XID *xid); 1576 : : +void binlog_post_rollback_by_xid(XID *xid); 1577 : : bool write_bin_log_start_alter(THD *thd, bool& partial_alter, 1578 : : uint64 start_alter_id, bool log_if_exists); 1579 : : #endif /* LOG_H */ ===== File: sql/log_cache.h ===== 41 : : class binlog_cache_data 42 : : { 43 : : public: 44 : 127320 : + binlog_cache_data(bool trx_cache, bool precompute_checksums): 45 : 127320 : + engine_binlog_info {0, 0, 0, 0, 0, 0, 0, 0}, 46 : 127320 : before_stmt_pos(MY_OFF_T_UNDEF), m_pending(0), status(0), 47 : 127320 : + is_trx_cache(trx_cache), 48 : 127320 : incident(FALSE), precompute_checksums(precompute_checksums), 49 : 127320 : saved_max_binlog_cache_size(0), ptr_binlog_cache_use(0), 50 : 127320 : ptr_binlog_cache_disk_use(0), m_file_reserved_bytes(0) 66 : 121739 : if (cache_log.file != -1 && !encrypt_tmp_files) 67 : 4424 : unlink(my_filename(cache_log.file)); 68 : : 69 : 121760 : + if (engine_binlog_info.engine_ptr) 70 : 2423 : + (*opt_binlog_engine_hton->binlog_oob_free) 71 : 2423 : + (engine_binlog_info.engine_ptr); 72 : 121759 : close_cached_file(&cache_log); 73 : 121664 : } 74 : : 75 : 272802 : + bool trx_cache() { return is_trx_cache; } 76 : : + 77 : : /* 78 : : Return 1 if there is no relevant entries in the cache 79 : : 138 : 1308226 : DBUG_ASSERT(empty()); 139 : 1308240 : } 140 : : 141 : 414334 : + void reset_for_engine_binlog() 142 : : + { 143 : 414334 : + bool cache_was_empty= empty(); 144 : : + 145 : 414334 : + if (engine_binlog_info.engine_ptr) 146 : 318092 : + (*opt_binlog_engine_hton->binlog_oob_reset) 147 : 318092 : + (&engine_binlog_info.engine_ptr); 148 : 414335 : + engine_binlog_info.engine_ptr2= nullptr; 149 : 414335 : + engine_binlog_info.xa_xid= nullptr; 150 : 414335 : + engine_binlog_info.out_of_band_offset= 0; 151 : 414335 : + engine_binlog_info.gtid_offset= 0; 152 : 414335 : + engine_binlog_info.internal_xa= false; 153 : : + /* Preserve the engine_ptr for the engine to re-use, was reset above. */ 154 : : + 155 : 414335 : + truncate(cache_log.pos_in_file); 156 : 414292 : + cache_log.pos_in_file= 0; 157 : 414292 : + cache_log.request_pos= cache_log.write_pos= cache_log.buffer; 158 : 414292 : + cache_log.write_end= cache_log.buffer + cache_log.buffer_length; 159 : 414292 : + checksum_opt= BINLOG_CHECKSUM_ALG_OFF; 160 : 414292 : + if (!cache_was_empty) 161 : 142189 : + compute_statistics(); 162 : 414292 : + status= 0; 163 : 414292 : + incident= FALSE; 164 : 414292 : + before_stmt_pos= MY_OFF_T_UNDEF; 165 : 414292 : + DBUG_ASSERT(empty()); 166 : 414305 : + } 167 : : + 168 : 675 : + void reset_cache_for_engine(my_off_t pos, 169 : : + int (*fct)(struct st_io_cache *, const uchar *, size_t)) 170 : : + { 171 : : + /* Bit fiddly here as we're abusing the IO_CACHE a bit for oob handling. */ 172 : 675 : + cache_log.pos_in_file= pos; 173 : 675 : + cache_log.request_pos= cache_log.write_pos= cache_log.buffer; 174 : 675 : + cache_log.write_end= 175 : 675 : + (cache_log.buffer + cache_log.buffer_length - (pos & (IO_SIZE-1))); 176 : 675 : + cache_log.write_function= fct; 177 : 675 : + } 178 : : + 179 : 2534954 : my_off_t get_byte_position() const 180 : : { 181 : 2534954 : DBUG_ASSERT(cache_log.type == WRITE_CACHE); 318 : : Cache to store data before copying it to the binary log. 319 : : */ 320 : : IO_CACHE cache_log; 321 : : + /* Context for engine-implemented binlogging. */ 322 : : + handler_binlog_event_group_info engine_binlog_info; 323 : : 324 : : protected: 325 : : /* 349 : : enum_binlog_checksum_alg checksum_opt; 350 : : 351 : : private: 352 : : + /* Record whether this is a stmt or trx cache. */ 353 : : + bool is_trx_cache; 354 : : /* 355 : : This indicates that some events did not get into the cache and most likely 356 : : it is corrupted. ===== File: sql/log_event.cc ===== 1289 : 3239337 : } 1290 : : 1291 : : 1292 : 721381 : +int handler_binlog_reader::read_log_event(String *packet, uint32_t ev_offset, 1293 : : + size_t max_allowed) 1294 : : +{ 1295 : 721381 : + uint32_t sofar= 0; 1296 : 721381 : + bool header_read= false; 1297 : 721381 : + uint32_t target_size= EVENT_LEN_OFFSET + 4; 1298 : : + int res; 1299 : : + 1300 : 721381 : + if (unlikely(!buf)) 1301 : 0 : + return LOG_READ_MEM; 1302 : : + 1303 : : + /* 1304 : : + Loop, first reading the "length" field, and then continuing to read data 1305 : : + until a full event has been placed in the packet. 1306 : : + */ 1307 : : + for (;;) 1308 : : + { 1309 : 1469255 : + if (buf_data_remain <= 0) 1310 : : + { 1311 : 52184 : + res= read_binlog_data(buf, BUF_SIZE); 1312 : 52184 : + if (res <= 0) 1313 : : + { 1314 : 9535 : + res= (res < 0 ? LOG_READ_IO : LOG_READ_EOF); 1315 : 9535 : + goto err; 1316 : : + } 1317 : 42649 : + buf_data_pos= 0; 1318 : 42649 : + buf_data_remain= res; 1319 : : + } 1320 : 1459720 : + uint32_t amount= std::min(target_size - sofar, buf_data_remain); 1321 : 1459720 : + packet->append((char *)buf + buf_data_pos, amount); 1322 : 1459720 : + buf_data_pos+= amount; 1323 : 1459720 : + buf_data_remain-= amount; 1324 : 1459720 : + sofar+= amount; 1325 : 1459720 : + if (target_size == sofar) 1326 : : + { 1327 : 1423692 : + if (header_read) 1328 : 711846 : + break; 1329 : : + else 1330 : : + { 1331 : 711846 : + header_read= true; 1332 : 711846 : + target_size= uint4korr(&((*packet)[EVENT_LEN_OFFSET + ev_offset])); 1333 : 711846 : + if (target_size < LOG_EVENT_MINIMAL_HEADER_LEN) 1334 : : + { 1335 : 0 : + res= LOG_READ_BOGUS; 1336 : 0 : + goto err; 1337 : : + } 1338 : 711846 : + else if (target_size > max_allowed) 1339 : : + { 1340 : 0 : + res= LOG_READ_TOO_LARGE; 1341 : 0 : + goto err; 1342 : : + } 1343 : : + /* 1344 : : + Note that here we rely on the fact that all valid events have more 1345 : : + data after the length. This way we avoid conditional for the 1346 : : + (useless) special case where we don't need to read anything more 1347 : : + after having read the first part. 1348 : : + */ 1349 : : + DBUG_ASSERT(LOG_EVENT_MINIMAL_HEADER_LEN > EVENT_LEN_OFFSET+4); 1350 : : + } 1351 : : + } 1352 : 747874 : + } 1353 : 711846 : + res= 0; /* Success */ 1354 : 721381 : +err: 1355 : 721381 : + return res; 1356 : : +} 1357 : : + 1358 : : 1359 : : /* 2 utility functions for the next method */ 1360 : : ===== File: sql/log_event.h ===== 587 : : #define MARIA_SLAVE_CAPABILITY_BINLOG_CHECKPOINT 3 588 : : /* MariaDB >= 10.0.1, which knows about global transaction id events. */ 589 : : #define MARIA_SLAVE_CAPABILITY_GTID 4 590 : : +/* MariaDB >= 12.2.1, basic engine-implemented binlog capability. */ 591 : : +#define MARIA_SLAVE_CAPABILITY_ENGINE_BINLOG 5 592 : : 593 : : /* Our capability. */ 594 : : +#define MARIA_SLAVE_CAPABILITY_MINE MARIA_SLAVE_CAPABILITY_ENGINE_BINLOG 595 : : 596 : : 597 : : /* 2584 : : #ifdef HAVE_REPLICATION 2585 : : void pack_info(Protocol* protocol) override; 2586 : : #endif /* HAVE_REPLICATION */ 2587 : : + bool to_packet(String *packet); 2588 : : #else 2589 : : bool print(FILE* file, PRINT_EVENT_INFO* print_event_info) override; 2590 : : #endif 3273 : : }; 3274 : : 3275 : : 3276 : : +#ifdef MYSQL_SERVER 3277 : : +/* 3278 : : + This is used to compute a compile-time constant max for the size (in bytes) 3279 : : + of a GTID event (Gtid_log_event::max_size). 3280 : : + 3281 : : + It is carefully written to take boolean parameters corresponding directly 3282 : : + to each conditional in Gtid_log_event::write(), so that the calculation here 3283 : : + will match the actual length computed by write(). 3284 : : + 3285 : : + Please ensure that that any new conditionals added in write() that affect 3286 : : + the event length are similarly extended with a boolean parameter for this 3287 : : + function so future code changes do not introduce incorrect result of this 3288 : : + function. 3289 : : +*/ 3290 : : +static constexpr uint32_t 3291 : 871011 : +cap_gtid_event_size(uint32_t proposed_size) 3292 : : +{ 3293 : : + /* This just because std::min is not constexpr in c++11. */ 3294 : : + return LOG_EVENT_HEADER_LEN + 3295 : 871011 : + (proposed_size < GTID_HEADER_LEN ? GTID_HEADER_LEN : proposed_size); 3296 : : +} 3297 : : +static constexpr uint32_t 3298 : 871011 : +get_gtid_event_size(bool fl_commit_id, bool fl_xa, bool fl_extra, 3299 : : + bool fl_multi_engine, bool fl_alter, 3300 : : + bool fl_thread_id, int bq_size, int gt_size) 3301 : : +{ 3302 : 871011 : + return cap_gtid_event_size((fl_commit_id ? GTID_HEADER_LEN + 2 : 13) + 3303 : 871011 : + (fl_xa ? 6 + bq_size + gt_size : 0) + 3304 : 871011 : + (fl_extra ? 1 : 0) + 3305 : 871011 : + (fl_multi_engine ? 1 : 0) + 3306 : 1742022 : + (fl_alter ? 8 : 0) + 3307 : 1742022 : + (fl_thread_id ? 4 : 0)); 3308 : : +} 3309 : : +#endif 3310 : : + 3311 : : /** 3312 : : @class Gtid_log_event 3313 : : 3447 : : involving multiple storage engines. No flag and extra data are added 3448 : : to the event when the transaction involves only one engine. 3449 : : */ 3450 : : + static constexpr uchar FL_EXTRA_MULTI_ENGINE_E1= 1; 3451 : : + static constexpr uchar FL_START_ALTER_E1= 2; 3452 : : + static constexpr uchar FL_COMMIT_ALTER_E1= 4; 3453 : : + static constexpr uchar FL_ROLLBACK_ALTER_E1= 8; 3454 : : + static constexpr uchar FL_EXTRA_THREAD_ID= 16; // thread_id like in BEGIN Query 3455 : : 3456 : : #ifdef MYSQL_SERVER 3457 : : + static constexpr uint32_t max_size= 3458 : : + get_gtid_event_size(FL_GROUP_COMMIT_ID, 3459 : : + (bool)(FL_PREPARED_XA|FL_COMPLETED_XA), 3460 : : + true, FL_EXTRA_MULTI_ENGINE_E1, 3461 : : + (bool)(FL_COMMIT_ALTER_E1|FL_ROLLBACK_ALTER_E1), 3462 : : + FL_EXTRA_THREAD_ID, MAXBQUALSIZE, MAXGTRIDSIZE); 3463 : : 3464 : : Gtid_log_event(THD *thd_arg, uint64 seq_no, uint32 domain_id, bool standalone, 3465 : : + enum_event_cache_type cache_type_arg, uint16 flags, 3466 : : + bool is_transactional, uint64 commit_id, 3467 : : + bool has_xid, bool is_ro_1pc); 3468 : : #ifdef HAVE_REPLICATION 3469 : : void pack_info(Protocol *protocol) override; 3470 : : int do_apply_event(rpl_group_info *rgi) override; 3478 : : const Format_description_log_event *description_event); 3479 : 1875214 : ~Gtid_log_event() = default; 3480 : 2423505 : Log_event_type get_type_code() override { return GTID_EVENT; } 3481 : 0 : int get_data_size() override 3482 : : { 3483 : 0 : return GTID_HEADER_LEN + ((flags2 & FL_GROUP_COMMIT_ID) ? 2 : 0); 3493 : : } 3494 : : 3495 : : #ifdef MYSQL_SERVER 3496 : : + uint32_t get_size() const noexcept; 3497 : : bool write(Log_event_writer *writer) override; 3498 : : static int make_compatible_event(String *packet, bool *need_dummy_event, 3499 : : ulong ev_offset, enum_binlog_checksum_alg checksum_alg); ===== File: sql/log_event_server.cc ===== 2453 : 9794 : } 2454 : : #endif /* defined(HAVE_REPLICATION) */ 2455 : : 2456 : : +bool 2457 : 217984 : +Format_description_log_event::to_packet(String *packet) 2458 : : { 2459 : : + uchar *p; 2460 : : + uint32 needed_length= 2461 : 217984 : + packet->length() + START_V3_HEADER_LEN + 1 + number_of_event_types + 1; 2462 : 217984 : + if (packet->reserve(needed_length)) 2463 : 0 : + return true; 2464 : 217984 : + p= (uchar *)packet->ptr() + packet->length();; 2465 : 217984 : + packet->length(needed_length); 2466 : 217985 : + int2store(p, binlog_version); 2467 : 217985 : + p += 2; 2468 : 217985 : + memcpy(p, server_version, ST_SERVER_VER_LEN); 2469 : 217985 : + p+= ST_SERVER_VER_LEN; 2470 : 217985 : if (!dont_set_created) 2471 : 49571 : created= get_time(); 2472 : 217985 : + int4store(p, created); 2473 : 217985 : + p+= 4; 2474 : 217985 : + *p++= common_header_len; 2475 : 217985 : + memcpy(p, post_header_len, number_of_event_types); 2476 : 217985 : + p+= number_of_event_types; 2477 : : + 2478 : : /* 2479 : : if checksum is requested 2480 : : record the checksum-algorithm descriptor next to 2499 : : (A), (V) presence in FD of the checksum-aware server makes the event 2500 : : 1 + 4 bytes bigger comparing to the former FD. 2501 : : */ 2502 : 217985 : + *p++= checksum_byte; 2503 : : + 2504 : 217985 : + return false; 2505 : : +} 2506 : : + 2507 : 216489 : +bool Format_description_log_event::write(Log_event_writer *writer) 2508 : : +{ 2509 : : + bool ret; 2510 : : + /* 2511 : : + We don't call Start_log_event_v::write() because this would make 2 2512 : : + my_b_safe_write(). 2513 : : + */ 2514 : 216489 : + constexpr uint32_t needed= START_V3_HEADER_LEN + 1 + LOG_EVENT_TYPES + 1; 2515 : : + char buff[needed + 1]; 2516 : 216489 : + String packet(buff, sizeof(buff), system_charset_info); 2517 : 216491 : + packet.length(0); 2518 : 216491 : + if (to_packet(&packet)) 2519 : 0 : + return true; 2520 : 216491 : + size_t rec_size= packet.length(); 2521 : 216491 : + DBUG_ASSERT(needed == rec_size); 2522 : : 2523 : 216490 : uint orig_checksum_len= writer->checksum_len; 2524 : 216490 : writer->checksum_len= BINLOG_CHECKSUM_LEN; 2525 : 216490 : ret= write_header(writer, rec_size) || 2526 : 432980 : + write_data(writer, packet.ptr(), packet.length()) || 2527 : 216491 : write_footer(writer); 2528 : 216490 : writer->checksum_len= orig_checksum_len; 2529 : 216490 : return ret; 2907 : : 2908 : 871014 : Gtid_log_event::Gtid_log_event(THD *thd_arg, uint64 seq_no_arg, 2909 : : uint32 domain_id_arg, bool standalone, 2910 : : + enum_event_cache_type cache_type_arg, 2911 : : uint16 flags_arg, bool is_transactional, 2912 : : uint64 commit_id_arg, bool has_xid, 2913 : 871014 : bool ro_1pc) 2918 : 871014 : flags_extra(0), extra_engines(0), 2919 : 871014 : thread_id(thd_arg->variables.pseudo_thread_id) 2920 : : { 2921 : 871014 : + cache_type= cache_type_arg; 2922 : 871014 : bool is_tmp_table= thd_arg->lex->stmt_accessed_temp_table(); 2923 : 1738549 : if (thd_arg->transaction->stmt.trans_did_wait() || 2924 : 867535 : thd_arg->transaction->all.trans_did_wait()) 2944 : 372227 : thd_arg->rgi_slave->gtid_ev_flags_extra & FL_EXTRA_THREAD_ID) 2945 : 852592 : flags_extra|= FL_EXTRA_THREAD_ID; 2946 : : 2947 : : + /* 2948 : : + When --binlog-storage-engine, we write the GTID event through the trx cache 2949 : : + (not directly to the binlog file), and we do not use this XA stuff in the 2950 : : + GTID event, that's handled by the engine binlog implementation. 2951 : : + */ 2952 : 871014 : + if (cache_type_arg == EVENT_NO_CACHE && is_transactional) 2953 : : { 2954 : 415194 : + XID_STATE &xid_state= thd->transaction->xid_state; 2955 : 473860 : if (xid_state.is_explicit_XA() && 2956 : 58666 : (thd->lex->sql_command == SQLCOM_XA_PREPARE || 2957 : 30679 : xid_state.get_state_code() == XA_PREPARED)) 3035 : : } 3036 : : 3037 : : 3038 : : +uint32_t 3039 : 871011 : +Gtid_log_event::get_size() const noexcept 3040 : : +{ 3041 : 871011 : + return get_gtid_event_size(flags2 & FL_GROUP_COMMIT_ID, 3042 : 871011 : + flags2 & (FL_PREPARED_XA | FL_COMPLETED_XA), 3043 : 871011 : + flags_extra > 0, 3044 : 871011 : + flags_extra & FL_EXTRA_MULTI_ENGINE_E1, 3045 : 871011 : + flags_extra & (FL_COMMIT_ALTER_E1 | FL_ROLLBACK_ALTER_E1), 3046 : 871011 : + flags_extra & FL_EXTRA_THREAD_ID, 3047 : 871011 : + xid.bqual_length, xid.gtrid_length); 3048 : : +} 3049 : : + 3050 : : + 3051 : : bool 3052 : 871015 : Gtid_log_event::write(Log_event_writer *writer) 3053 : : { 3054 : : + uchar buf[max_size]; 3055 : 871015 : size_t write_len= 13; 3056 : : 3057 : 871015 : int8store(buf, seq_no); 3152 : 28 : return write_footer(writer); 3153 : : } 3154 : : 3155 : : + /* 3156 : : + Whenever updating this function, make sure that Gtid_log_event::get_size() 3157 : : + still computes the same consistent event length! Do not just rely on this 3158 : : + assertion, in case test coverage is not 100%. 3159 : : + */ 3160 : 870987 : + DBUG_ASSERT(DBUG_IF("negate_xid_from_gtid") || 3161 : : + DBUG_IF("negate_xid_data_from_gtid") || 3162 : : + DBUG_IF("negate_alter_fl_from_gtid") || 3163 : : + DBUG_IF("inject_fl_extra_multi_engine_into_gtid") || 3164 : : + write_len + LOG_EVENT_HEADER_LEN == get_size()); 3165 : : + 3166 : 870987 : return write_header(writer, write_len) || 3167 : 1741974 : write_data(writer, buf, write_len) || 3168 : 1741974 : write_footer(writer); ===== File: sql/mysqld.cc ===== 368 : : /* Global variables */ 369 : : 370 : : bool opt_bin_log, opt_bin_log_used=0, opt_ignore_builtin_innodb= 0; 371 : : +static bool opt_bin_log_nonempty, opt_bin_log_path; 372 : : +char *opt_binlog_storage_engine= const_cast(""); 373 : : +static plugin_ref opt_binlog_engine_plugin; 374 : : +const char *opt_binlog_directory; 375 : : +handlerton *opt_binlog_engine_hton; 376 : : bool opt_bin_log_compress; 377 : : uint opt_bin_log_compress_min_len; 378 : : my_bool opt_log, debug_assert_if_crashed_table= 0, opt_help= 0; 778 : : mysql_prlock_t LOCK_system_variables_hash; 779 : : mysql_cond_t COND_start_thread; 780 : : pthread_t signal_thread; 781 : : +bool signal_thread_needs_join= false; 782 : : pthread_attr_t connection_attrib; 783 : : mysql_mutex_t LOCK_server_started; 784 : : mysql_cond_t COND_server_started; 945 : : key_LOCK_pending_checkpoint; 946 : : #endif /* HAVE_MMAP */ 947 : : 948 : : +PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_binlog_use, 949 : : + key_BINLOG_LOCK_xid_list, 950 : : key_BINLOG_LOCK_binlog_background_thread, 951 : : key_LOCK_binlog_end_pos, 952 : : key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi, 1009 : : #endif /* HAVE_des */ 1010 : : 1011 : : { &key_BINLOG_LOCK_index, "MYSQL_BIN_LOG::LOCK_index", 0}, 1012 : : + { &key_BINLOG_LOCK_binlog_use, "MYSQL_BIN_LOG::LOCK_binlog_use", 0}, 1013 : : { &key_BINLOG_LOCK_xid_list, "MYSQL_BIN_LOG::LOCK_xid_list", 0}, 1014 : : { &key_BINLOG_LOCK_binlog_background_thread, "MYSQL_BIN_LOG::LOCK_binlog_background_thread", 0}, 1015 : : { &key_LOCK_binlog_end_pos, "MYSQL_BIN_LOG::LOCK_binlog_end_pos", 0 }, 1112 : : PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; 1113 : : #endif /* HAVE_MMAP */ 1114 : : 1115 : : +PSI_cond_key key_BINLOG_COND_binlog_use, key_BINLOG_COND_xid_list, 1116 : : key_BINLOG_COND_bin_log_updated, key_BINLOG_COND_relay_log_updated, 1117 : : key_BINLOG_COND_binlog_background_thread, 1118 : : key_BINLOG_COND_binlog_background_thread_end, 1149 : : { &key_TC_LOG_MMAP_COND_queue_busy, "TC_LOG_MMAP::COND_queue_busy", 0}, 1150 : : #endif /* HAVE_MMAP */ 1151 : : { &key_BINLOG_COND_bin_log_updated, "MYSQL_BIN_LOG::COND_bin_log_updated", 0}, { &key_BINLOG_COND_relay_log_updated, "MYSQL_BIN_LOG::COND_relay_log_updated", 0}, 1152 : : + { &key_BINLOG_COND_binlog_use, "MYSQL_BIN_LOG::COND_binlog_use", 0}, 1153 : : { &key_BINLOG_COND_xid_list, "MYSQL_BIN_LOG::COND_xid_list", 0}, 1154 : : { &key_BINLOG_COND_binlog_background_thread, "MYSQL_BIN_LOG::COND_binlog_background_thread", 0}, 1155 : : { &key_BINLOG_COND_binlog_background_thread_end, "MYSQL_BIN_LOG::COND_binlog_background_thread_end", 0}, 1999 : 11802 : injector::free_instance(); 2000 : 11802 : mysql_bin_log.cleanup(); 2001 : 11802 : Gtid_index_writer::gtid_index_cleanup(); 2002 : 11802 : + if (opt_binlog_engine_plugin) 2003 : 161 : + plugin_unlock(0, opt_binlog_engine_plugin); 2004 : : 2005 : 11802 : my_tz_free(); 2006 : 11802 : my_dboptions_cache_free(); 2133 : : { 2134 : 2 : sql_print_warning("Signal handler thread did not exit in a timely manner. " 2135 : : "Continuing to wait for it to stop.."); 2136 : : + } 2137 : 11814 : + if (signal_thread_needs_join) 2138 : : + { 2139 : 11642 : pthread_join(signal_thread, NULL); 2140 : 11642 : + signal_thread_needs_join= false; 2141 : : } 2142 : : #endif 2143 : 11814 : } 3236 : 0 : error,errno); 3237 : 0 : exit(1); 3238 : : } 3239 : 14966 : + signal_thread_needs_join= true; 3240 : 14966 : mysql_cond_wait(&COND_start_thread, &LOCK_start_thread); 3241 : 14966 : mysql_mutex_unlock(&LOCK_start_thread); 3242 : : 4966 : : { option, OPT_REMOVED_OPTION, \ 4967 : : 0, 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0 } 4968 : : 4969 : : + 4970 : : +static int 4971 : 16 : +create_dir_path_if_needed(const char *dir) 4972 : : +{ 4973 : : + MY_STAT stat_buf; 4974 : : + char buf[FN_REFLEN]; 4975 : 16 : + char *end= strmake(buf, dir, FN_REFLEN-1); 4976 : 16 : + size_t len= dirname_length(buf); 4977 : 16 : + if (len > 0 && end == buf + len) 4978 : : + { 4979 : : + /* Ends in trailing '/', strip it. */ 4980 : 0 : + buf[len-1]= '\0'; 4981 : 0 : + len= dirname_length(buf); 4982 : : + } 4983 : 16 : + if (my_stat(dir, &stat_buf, MYF(0))) 4984 : 8 : + return 0; // Already exists 4985 : 8 : + if (len > 1) 4986 : : + { 4987 : : + /* Create any parent directory as well. */ 4988 : 0 : + strmake(buf, dir, len); 4989 : 0 : + if (create_dir_path_if_needed(buf)) 4990 : 0 : + return 1; 4991 : : + } 4992 : 8 : + if(my_mkdir(dir, 0777, MYF(MY_WME)) && my_errno != EEXIST) 4993 : 0 : + return 1; 4994 : 8 : + return 0; 4995 : : +} 4996 : : + 4997 : : + 4998 : 15136 : static int init_server_components() 4999 : : { 5000 : 15136 : DBUG_ENTER("init_server_components"); 5001 : 15136 : + bool binlog_engine_used= false; 5002 : : + 5003 : : /* 5004 : : We need to call each of these following functions to ensure that 5005 : : all things are initialized so that unireg_abort() doesn't fail 5172 : : 5173 : 15136 : if (opt_bin_log) 5174 : : { 5175 : 7788 : + if (opt_binlog_storage_engine && *opt_binlog_storage_engine) 5176 : 169 : + binlog_engine_used= true; 5177 : : + 5178 : : /* Reports an error and aborts, if the --log-bin's path 5179 : : is a directory.*/ 5180 : 7788 : if (opt_bin_logname[0] && 5181 : 7472 : opt_bin_logname[strlen(opt_bin_logname) - 1] == FN_LIBCHAR) 5182 : : { 5183 : 0 : sql_print_error("Path '%s' is a directory name, please specify " 5184 : : + "a file name for --log-bin option, or use " 5185 : : + "--binlog-directory", opt_bin_logname); 5186 : 0 : unireg_abort(1); 5187 : : } 5188 : : 5189 : 7788 : + if (!binlog_engine_used) 5190 : : { 5191 : : + /* Reports an error and aborts, if the --log-bin-index's path 5192 : : + is a directory.*/ 5193 : 7619 : + if (opt_binlog_index_name && 5194 : 7619 : + opt_binlog_index_name[strlen(opt_binlog_index_name) - 1] 5195 : : + == FN_LIBCHAR) 5196 : : + { 5197 : 0 : + sql_print_error("Path '%s' is a directory name, please specify " 5198 : : + "a file name for --log-bin-index option", 5199 : : + opt_binlog_index_name); 5200 : 0 : + unireg_abort(1); 5201 : : + } 5202 : : } 5203 : : 5204 : : + char buf[FN_REFLEN], buf2[FN_REFLEN]; 5205 : : const char *ln; 5206 : 7788 : ln= mysql_bin_log.generate_name(opt_bin_logname, "-bin", 1, buf); 5207 : : + /* Add in opt_binlog_directory, if given. */ 5208 : 7788 : + if (opt_binlog_directory && opt_binlog_directory[0]) 5209 : : + { 5210 : 16 : + if (strlen(opt_binlog_directory) + 1 + strlen(ln) + 1 > FN_REFLEN) 5211 : : + { 5212 : 0 : + sql_print_error("The combination of --binlog-directory path '%s' with " 5213 : : + "filename '%s' from --log-bin results in a too long " 5214 : : + "path", opt_binlog_directory, ln); 5215 : 0 : + unireg_abort(1); 5216 : : + } 5217 : 16 : + if (create_dir_path_if_needed(opt_binlog_directory)) 5218 : : + { 5219 : 0 : + sql_print_error("Failed to create the directory '%s' specified in " 5220 : : + "--binlog-directory, error code: %d", 5221 : 0 : + opt_binlog_directory, my_errno); 5222 : 0 : + unireg_abort(1); 5223 : : + } 5224 : 16 : + const char *end= &buf2[FN_REFLEN-1]; 5225 : 16 : + char *p= strmake(buf2, opt_binlog_directory, FN_REFLEN - 2); 5226 : 16 : + *p++= FN_LIBCHAR; 5227 : 16 : + strmake(p, ln, end - p - 1); 5228 : 16 : + ln= buf2; 5229 : : + } 5230 : 7788 : + if (!binlog_engine_used && !opt_bin_logname[0] && !opt_binlog_index_name) 5231 : : { 5232 : : /* 5233 : : User didn't give us info to name the binlog index file. 5246 : : } 5247 : 7788 : if (ln == buf) 5248 : 7772 : opt_bin_logname= my_once_strdup(buf, MYF(MY_WME)); 5249 : 16 : + else if (ln == buf2) 5250 : 16 : + opt_bin_logname= my_once_strdup(buf2, MYF(MY_WME)); 5251 : : } 5252 : : 5253 : : /* 5280 : : 5281 : 15136 : if (WSREP_ON && !wsrep_recovery && !opt_abort) 5282 : : { 5283 : 742 : + if (binlog_engine_used) 5284 : : + { 5285 : 0 : + sql_print_error("Galera cannot be used with the " 5286 : : + "--binlog-storage-engine option"); 5287 : 0 : + unireg_abort(1); 5288 : : + } 5289 : 742 : if (opt_bootstrap) // bootstrap option given - disable wsrep functionality 5290 : : { 5291 : 0 : wsrep_provider_init(WSREP_NONE); 5318 : : } 5319 : : #endif /* WITH_WSREP */ 5320 : : 5321 : 15132 : + if (!opt_help && !binlog_engine_used && opt_bin_log) 5322 : : { 5323 : 7619 : if (mysql_bin_log.open_index_file(opt_binlog_index_name, opt_bin_logname, 5324 : : TRUE)) 5639 : 15077 : if (init_gtid_pos_auto_engines()) 5640 : 0 : unireg_abort(1); 5641 : : 5642 : 15077 : + if (opt_binlog_directory && opt_binlog_directory[0] && 5643 : : + opt_bin_log_path) 5644 : : + { 5645 : 0 : + sql_print_error("Cannot specify a directory path for the binlog in " 5646 : : + "--log-bin when --binlog-directory-path is also used"); 5647 : 0 : + unireg_abort(1); 5648 : : + } 5649 : : + 5650 : 15077 : + if (binlog_engine_used) 5651 : : + { 5652 : 169 : + LEX_CSTRING name= { opt_binlog_storage_engine, strlen(opt_binlog_storage_engine) }; 5653 : 169 : + opt_binlog_engine_plugin= ha_resolve_by_name(0, &name, false); 5654 : 338 : + if (!opt_binlog_engine_plugin || 5655 : 169 : + !ha_storage_engine_is_enabled(opt_binlog_engine_hton= 5656 : 169 : + plugin_hton(opt_binlog_engine_plugin))) 5657 : : + { 5658 : 0 : + if (!opt_binlog_engine_plugin) 5659 : 0 : + sql_print_error("Unknown/unsupported storage engine: %s", 5660 : : + opt_binlog_storage_engine); 5661 : : + else 5662 : 0 : + sql_print_error("Engine %s is not available for " 5663 : : + "--binlog-storage-engine", 5664 : : + opt_binlog_storage_engine); 5665 : 0 : + unireg_abort(1); 5666 : : + } 5667 : 169 : + if (!opt_binlog_engine_hton->binlog_write_direct || 5668 : 169 : + !opt_binlog_engine_hton->get_binlog_reader) 5669 : : + { 5670 : 0 : + sql_print_error("Engine %s does not support --binlog-storage-engine", 5671 : : + opt_binlog_storage_engine); 5672 : 0 : + unireg_abort(1); 5673 : : + } 5674 : : + 5675 : 169 : + if (opt_bin_log_nonempty) 5676 : : + { 5677 : 0 : + sql_print_error("Binlog name can not be set with --log-bin=NAME when " 5678 : : + "--binlog-storage-engine is used. Use --log-bin " 5679 : : + "(without argument) to enable the binlog, and use " 5680 : : + "--binlog-directory to specify a separate directory " 5681 : : + "for binlogs"); 5682 : 0 : + unireg_abort(1); 5683 : : + } 5684 : : +#ifdef HAVE_REPLICATION 5685 : 169 : + if (rpl_semi_sync_master_enabled) 5686 : : + { 5687 : 0 : + sql_print_error("Semi-synchronous replication is not yet supported " 5688 : : + "with --binlog-storage-engine"); 5689 : 0 : + unireg_abort(1); 5690 : : + } 5691 : 169 : + if (rpl_status != RPL_AUTH_MASTER) 5692 : : + { 5693 : 0 : + sql_print_error("The --init-rpl-role option is not available with " 5694 : : + "--binlog-storage-engine"); 5695 : 0 : + unireg_abort(1); 5696 : : + } 5697 : 169 : + if (encrypt_binlog) 5698 : : + { 5699 : 0 : + sql_print_error("Binlog encryption is not available with " 5700 : : + "--binlog-storage-engine. Using full-disk encryption on " 5701 : : + "the operating system level is recommended instead"); 5702 : 0 : + unireg_abort(1); 5703 : : + } 5704 : : +#endif 5705 : : + } 5706 : : + 5707 : : #ifdef USE_ARIA_FOR_TMP_TABLES 5708 : 15077 : if (!ha_storage_engine_is_enabled(maria_hton) && !opt_bootstrap) 5709 : : { 5736 : 15077 : start_handle_manager(); 5737 : : #endif 5738 : : 5739 : : + /* 5740 : : + When binlog is stored in InnoDB, checksums are done on the page level, so 5741 : : + set the default for per-event checksums to OFF. 5742 : : + */ 5743 : 15077 : + if (opt_binlog_engine_hton) 5744 : 169 : + binlog_checksum_options= 0; 5745 : : + 5746 : 15077 : tc_log= get_tc_log_implementation(); 5747 : : 5748 : 15077 : if (tc_log->open(opt_bin_log ? opt_bin_logname : opt_tc_log_file)) 5758 : : 5759 : 15057 : if (opt_bin_log) 5760 : : { 5761 : 7768 : mysql_mutex_t *log_lock= mysql_bin_log.get_log_lock(); 5762 : : + bool error; 5763 : 7768 : mysql_mutex_lock(log_lock); 5764 : 7768 : + if (opt_binlog_engine_hton) 5765 : : + { 5766 : 169 : + error= mysql_bin_log.open_engine(opt_binlog_engine_hton, max_binlog_size, 5767 : : + opt_binlog_directory); 5768 : : + } 5769 : : + else 5770 : : + { 5771 : 7599 : + error= mysql_bin_log.open(opt_bin_logname, 0, 0, 5772 : : + WRITE_CACHE, max_binlog_size, 0, TRUE); 5773 : : + } 5774 : 7768 : mysql_mutex_unlock(log_lock); 5775 : 7768 : if (unlikely(error)) 5776 : 0 : unireg_abort(1); 5777 : : } 5778 : : 5779 : 15057 : + if (!binlog_engine_used && unlikely(init_binlog_cache_dir())) 5780 : 0 : unireg_abort(1); 5781 : : 5782 : : #ifdef HAVE_REPLICATION 5786 : : 5787 : 15057 : if (opt_bin_log) 5788 : : { 5789 : 7768 : + if (!opt_binlog_engine_hton) 5790 : : + { 5791 : 7599 : + if (binlog_space_limit) 5792 : 8 : + mysql_bin_log.count_binlog_space_with_mutex(); 5793 : 7599 : + mysql_bin_log.purge(1); 5794 : : + } 5795 : : } 5796 : : else 5797 : : { 6873 : : &debug_assert_on_not_freed_memory, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 6874 : : 0}, 6875 : : #endif /* DBUG_OFF */ 6876 : : {"default-storage-engine", 0, "The default storage engine for new tables", 6877 : : &default_storage_engine, 0, 0, GET_STR, REQUIRED_ARG, 6878 : : 0, 0, 0, 0, 0, 0 }, 8451 : 10677 : case (int) OPT_BIN_LOG: 8452 : 10677 : opt_bin_log= MY_TEST(argument != disabled_my_option); 8453 : 10677 : opt_bin_log_used= 1; 8454 : 10677 : + opt_bin_log_nonempty= (argument && argument[0]); 8455 : 21001 : + opt_bin_log_path= argument && 8456 : 10324 : + (strchr(argument, FN_LIBCHAR) || strchr(argument, FN_LIBCHAR2)); 8457 : 10677 : break; 8458 : 17589 : case (int) OPT_LOG_BASENAME: 8459 : : { 9795 : : PSI_stage_info stage_starting= { 0, "starting", 0}; 9796 : : PSI_stage_info stage_waiting_for_flush= { 0, "Waiting for non trans tables to be flushed", 0}; 9797 : : PSI_stage_info stage_waiting_for_ddl= { 0, "Waiting for DDLs", 0}; 9798 : : +PSI_stage_info stage_waiting_for_reset_master= { 0, "Waiting for a running RESET MASTER to complete", 0}; 9799 : : 9800 : : #ifdef WITH_WSREP 9801 : : // Additional Galera thread states 10026 : : & stage_waiting_for_semi_sync_slave, 10027 : : & stage_reading_semi_sync_ack, 10028 : : & stage_waiting_for_deadlock_kill, 10029 : : + & stage_starting, 10030 : : + & stage_waiting_for_reset_master 10031 : : #ifdef WITH_WSREP 10032 : : , 10033 : : & stage_waiting_isolation, ===== File: sql/mysqld.h ===== 109 : : 110 : : extern bool opt_large_files; 111 : : extern bool opt_bin_log, opt_error_log, opt_bin_log_compress; 112 : : +extern char *opt_binlog_storage_engine; 113 : : +extern const char *opt_binlog_directory; 114 : : +extern handlerton *opt_binlog_engine_hton; 115 : : extern uint opt_bin_log_compress_min_len; 116 : : extern my_bool opt_log, opt_bootstrap; 117 : : extern my_bool opt_support_flashback; 316 : : key_LOCK_pool, key_LOCK_pending_checkpoint; 317 : : #endif /* HAVE_MMAP */ 318 : : 319 : : +extern PSI_mutex_key key_BINLOG_LOCK_index, key_BINLOG_LOCK_binlog_use, 320 : : + key_BINLOG_LOCK_xid_list, 321 : : key_BINLOG_LOCK_binlog_background_thread, 322 : : key_LOCK_binlog_end_pos, 323 : : key_delayed_insert_mutex, key_hash_filo_lock, key_LOCK_active_mi, 363 : : extern PSI_cond_key key_PAGE_cond, key_COND_active, key_COND_pool; 364 : : #endif /* HAVE_MMAP */ 365 : : 366 : : +extern PSI_cond_key key_BINLOG_COND_binlog_use, key_BINLOG_COND_xid_list, 367 : : key_BINLOG_COND_binlog_background_thread, 368 : : key_BINLOG_COND_binlog_background_thread_end, 369 : : key_COND_cache_status_changed, key_COND_manager, key_COND_server_started, 647 : : extern PSI_stage_info stage_slave_background_wait_request; 648 : : extern PSI_stage_info stage_waiting_for_deadlock_kill; 649 : : extern PSI_stage_info stage_starting; 650 : : +extern PSI_stage_info stage_waiting_for_reset_master; 651 : : #ifdef WITH_WSREP 652 : : // Additional Galera thread states 653 : : extern PSI_stage_info stage_waiting_isolation; ===== File: sql/online_alter.cc ===== 63 : : public binlog_cache_data 64 : : { 65 : : public: 66 : 497 : + online_alter_cache_data() : binlog_cache_data(false, false), 67 : 497 : hton(nullptr), sink_log(nullptr) { } 68 : 3478 : void store_prev_position() 69 : : { ===== File: sql/rpl_gtid.cc ===== 1579 : : bool 1580 : 44442 : rpl_binlog_state_base::load_nolock(rpl_binlog_state_base *orig_state) 1581 : : { 1582 : 44442 : reset_nolock(); 1583 : 44442 : + return orig_state->iterate( 1584 : 457252 : + [this] (const rpl_gtid *gtid) { 1585 : 457252 : + return update_nolock(gtid); 1586 : 44442 : + }); 1587 : : } 1588 : : 1589 : : 1672 : : int 1673 : 115384 : rpl_binlog_state_base::get_gtid_list_nolock(rpl_gtid *gtid_list, uint32 list_size) 1674 : : { 1675 : 115384 : + uint32_t pos= 0; 1676 : 115384 : + return iterate( 1677 : 4471268 : + [>id_list, list_size, &pos] (const rpl_gtid *gtid) { 1678 : 2235634 : if (pos >= list_size) 1679 : 0 : + return true; 1680 : 2235634 : memcpy(>id_list[pos++], gtid, sizeof(*gtid)); 1681 : 2235634 : + return false; 1682 : 115384 : + }); 1683 : : } 1684 : : 1685 : : 2030 : : int 2031 : 4857 : rpl_binlog_state::write_to_iocache(IO_CACHE *dest) 2032 : : { 2033 : : char buf[21]; 2034 : : 2035 : 4857 : mysql_mutex_lock(&LOCK_binlog_state); 2036 : : 2037 : 4857 : + int res= iterate([&buf, dest] (const rpl_gtid *gtid) { 2038 : 5571 : + longlong10_to_str(gtid->seq_no, buf, 10); 2039 : 5571 : + if (my_b_printf(dest, "%u-%u-%s\n", gtid->domain_id, gtid->server_id, buf)) 2040 : 0 : + return true; 2041 : 5571 : + return false; 2042 : 4857 : + }); 2043 : : 2044 : 4857 : mysql_mutex_unlock(&LOCK_binlog_state); 2045 : 4857 : return res; 2046 : : } 2192 : : bool 2193 : 18599 : rpl_binlog_state::append_state(String *str) 2194 : : { 2195 : 18599 : mysql_mutex_lock(&LOCK_binlog_state); 2196 : 18599 : reset_dynamic(>id_sort_array); 2197 : : 2198 : 18599 : + bool res= iterate([this] (const rpl_gtid *gtid) { 2199 : 144986 : + if (insert_dynamic(>id_sort_array, (const void *) gtid)) 2200 : 0 : + return true; 2201 : 144986 : + return false; 2202 : : + }); 2203 : : 2204 : 18599 : + if (likely(!res)) 2205 : 18599 : + rpl_slave_state_tostring_helper(>id_sort_array, str); 2206 : : 2207 : 18599 : mysql_mutex_unlock(&LOCK_binlog_state); 2208 : 18599 : return res; 2209 : : } 2211 : : /** 2212 : : Remove domains supplied by the first argument from binlog state. 2213 : : Removal is done for any domain whose last gtids (from all its servers) match 2214 : : + ones in the binlog state at the start of the current binlog, passed in as the 2215 : : + 2nd argument. 2216 : : 2217 : : @param ids gtid domain id sequence, may contain dups 2218 : : + @param init_state Binlog state at the start of the current binlog 2219 : : @param errbuf [out] pointer to possible error message array 2220 : : 2221 : : @retval NULL as success when at least one domain is removed 2225 : : */ 2226 : : const char* 2227 : 40 : rpl_binlog_state::drop_domain(DYNAMIC_ARRAY *ids, 2228 : : + rpl_binlog_state_base *init_state, 2229 : : char* errbuf) 2230 : : { 2231 : : DYNAMIC_ARRAY domain_unique; // sequence (unsorted) of unique element*:s 2232 : : rpl_binlog_state::element* domain_unique_buffer[16]; 2233 : : + ulong k; 2234 : 40 : const char* errmsg= NULL; 2235 : : 2236 : 40 : DBUG_ENTER("rpl_binlog_state::drop_domain"); 2257 : : B and C may require the user's attention so any (incl the A's suspected) 2258 : : inconsistency is diagnosed and *warned*. 2259 : : */ 2260 : : + 2261 : 40 : + errbuf[0]= 0; 2262 : 40 : + init_state->iterate([this, errbuf](const rpl_gtid *gtid) { 2263 : 252 : + rpl_gtid* rb_state_gtid= find_nolock(gtid->domain_id, gtid->server_id); 2264 : 252 : if (!rb_state_gtid) 2265 : 4 : sprintf(errbuf, 2266 : : "missing gtids from the '%u-%u' domain-server pair which is " 2267 : : "referred to in the gtid list describing an earlier state. Ignore " 2268 : : "if the domain ('%u') was already explicitly deleted", 2269 : 4 : + gtid->domain_id, gtid->server_id, 2270 : 4 : + gtid->domain_id); 2271 : 248 : + else if (rb_state_gtid->seq_no < gtid->seq_no) 2272 : 2 : sprintf(errbuf, 2273 : : "having a gtid '%u-%u-%llu' which is less than " 2274 : : "the '%u-%u-%llu' of the gtid list describing an earlier state. " 2275 : : "The state may have been affected by manually injecting " 2276 : : "a lower sequence number gtid or via replication", 2277 : : rb_state_gtid->domain_id, rb_state_gtid->server_id, 2278 : 2 : + rb_state_gtid->seq_no, gtid->domain_id, 2279 : 2 : + gtid->server_id, gtid->seq_no); 2280 : 252 : if (strlen(errbuf)) // use strlen() as cheap flag 2281 : 6 : push_warning_printf(current_thd, Sql_condition::WARN_LEVEL_WARN, 2282 : : ER_BINLOG_CANT_DELETE_GTID_DOMAIN, 2283 : : "The current gtid binlog state is incompatible with " 2284 : : "a former one %s.", errbuf); 2285 : 252 : + errbuf[0]= 0; 2286 : 252 : + return false; // No error 2287 : : + }); 2288 : : 2289 : : /* 2290 : : For each domain_id from ids 2291 : : If the domain is already absent from the binlog state 2292 : : Warn && continue 2293 : : + If any GTID with that domain in binlog state is missing from init_state 2294 : : Error out binlog state can't change 2295 : : */ 2296 : 122 : for (ulong i= 0; i < ids->elements; i++) 2297 : : { 2298 : 92 : rpl_binlog_state::element *elem= NULL; 2299 : : uint32 *ptr_domain_id; 2300 : : 2301 : 92 : ptr_domain_id= (uint32*) dynamic_array_ptr(ids, i); 2302 : 92 : elem= (rpl_binlog_state::element *) 2311 : 12 : continue; 2312 : : } 2313 : : 2314 : 248 : + for (k= 0; k < elem->hash.records; k++) 2315 : : { 2316 : 178 : rpl_gtid *d_gtid= (rpl_gtid *)my_hash_element(&elem->hash, k); 2317 : : + rpl_gtid *state_gtid= 2318 : 178 : + init_state->find_nolock(d_gtid->domain_id, d_gtid->server_id); 2319 : 178 : + if (!state_gtid || state_gtid->seq_no != d_gtid->seq_no) 2320 : : + { 2321 : 10 : + sprintf(errbuf, "binlog files may contain gtids from the domain ('%u') " 2322 : : + "being deleted. Make sure to first purge those files", 2323 : : + *ptr_domain_id); 2324 : 10 : + errmsg= errbuf; 2325 : 10 : + goto end; 2326 : : + } 2327 : : } 2328 : : 2329 : : // compose a sequence of unique pointers to domain object 2330 : 384 : for (k= 0; k < domain_unique.elements; k++) 2331 : : { ===== File: sql/rpl_gtid.h ===== 16 : : #ifndef RPL_GTID_H 17 : : #define RPL_GTID_H 18 : : 19 : : #include "queues.h" 20 : : #include 21 : : +#include "rpl_gtid_base.h" 22 : : + 23 : : 24 : : /* Definitions for MariaDB global transaction ID (GTID). */ 25 : : 30 : : #ifdef MYSQL_SERVER 31 : : struct TABLE; 32 : : #endif 33 : : 34 : : #define GTID_MAX_STR_LENGTH (10+1+10+1+20) 35 : : +#define PARAM_GTID(G) (G).domain_id, (G).server_id, (G).seq_no 36 : : 37 : 0 : inline bool operator==(const rpl_gtid& lhs, const rpl_gtid& rhs) 38 : : { 298 : : rpl_binlog_state builds server logic on top of that like mutex locking, 299 : : gtid_strict_mode handling, etc. 300 : : */ 301 : : struct rpl_binlog_state : public rpl_binlog_state_base 302 : : { 303 : : /* Mutex protecting access to the state. */ 329 : : bool append_state(String *str); 330 : : rpl_gtid *find(uint32 domain_id, uint32 server_id); 331 : : rpl_gtid *find_most_recent(uint32 domain_id); 332 : : + const char* drop_domain(DYNAMIC_ARRAY *ids, rpl_binlog_state_base *init_state, 333 : : + char*); 334 : : }; 335 : : 336 : : ===== File: sql/rpl_mi.cc ===== 37 : 38598 : rli(is_slave_recovery), port(MYSQL_PORT), 38 : 19299 : checksum_alg_before_fd(BINLOG_CHECKSUM_ALG_UNDEF), 39 : 19299 : connect_retry(DEFAULT_CONNECT_RETRY), retry_count(master_retry_count), 40 : 19299 : + connects_tried(0), binlog_storage_engine(0), inited(0), abort_slave(0), 41 : 19299 : slave_running(MYSQL_SLAVE_NOT_RUN), slave_run_id(0), 42 : 19299 : clock_diff_with_master(0), 43 : 19299 : sync_counter(0), heartbeat_period(0), received_heartbeats(0), ===== File: sql/rpl_mi.h ===== 269 : : bool dbug_do_disconnect; 270 : : int dbug_event_counter; 271 : : #endif 272 : : + /* Whether the master is using --binlog-storage-engine. */ 273 : : + bool binlog_storage_engine; 274 : : bool inited; 275 : : volatile bool abort_slave; 276 : : volatile uint slave_running; ===== File: sql/share/errmsg-utf8.txt ===== 12362 : : eng "Hint %s is ignored as conflicting/duplicated (an index hint of the same type or opposite kind has already been specified for the key)" 12363 : : ER_WARN_NO_IMPLICIT_QB_NAMES_IN_VIEW 12364 : : eng "Implicit query block names are ignored for hints specified within VIEWs" 12365 : : +ER_BINLOG_IN_USE 12366 : : + eng "Cannot execute RESET MASTER as the binlog is in use by a connected slave or other RESET MASTER or binlog reader. Check SHOW PROCESSLIST for "Binlog Dump" commands and use KILL to stop such readers" 12367 : : +ER_BINLOG_IN_USE_TRX 12368 : : + eng "Cannot execute RESET MASTER as the binlog is in use by an active transaction" 12369 : : +ER_CANNOT_INIT_ENGINE_BINLOG_READER 12370 : : + eng "Cannot initialize binlog reader from storage engine %s" 12371 : : +ER_ENGINE_BINLOG_REQUIRES_GTID 12372 : : + eng "GTID starting position is required on master with --binlog-storage-engine enabled" 12373 : : +ER_NOT_AVAILABLE_WITH_ENGINE_BINLOG 12374 : : + eng "%s is not available when --binlog-storage-engine is enabled" 12375 : : +ER_NOT_YET_SUPPORTED_ENGINE_BINLOG 12376 : : + eng "%s is not yet supported with --binlog-storage-engine" 12377 : : +ER_ENGINE_BINLOG_NO_DELETE_DOMAIN 12378 : : + eng "The binlog engine does not support DELETE_DOMAIN_ID" 12379 : : +ER_BINLOG_CANNOT_READ_STATE 12380 : : + eng "Error reading GTID state from the binlog" 12381 : : +ER_BINLOG_POS_INVALID 12382 : : + eng "The binlog offset %llu is invalid" 12383 : : +ER_READING_BINLOG_FILE 12384 : : + eng "Error reading from page number %u in binlog file (error code: %d)" ===== File: sql/slave.cc ===== 2261 : : } 2262 : : } 2263 : : 2264 : : + /* 2265 : : + See if the master is using the new binlog format from 2266 : : + --binlog-storage-engine. 2267 : : + */ 2268 : 13051 : + if (mysql_real_query(mysql, 2269 : 13051 : + STRING_WITH_LEN("SELECT @@GLOBAL.binlog_storage_engine")) || 2270 : 26102 : + !(master_res= mysql_store_result(mysql)) || 2271 : 13051 : + !(master_row= mysql_fetch_row(master_res))) 2272 : : + { 2273 : 0 : + if (check_io_slave_killed(mi, NULL)) 2274 : 0 : + goto slave_killed_err; 2275 : : + 2276 : 0 : + err_code= mysql_errno(mysql); 2277 : 0 : + if (is_network_error(err_code)) 2278 : : + { 2279 : 0 : + mi->report(ERROR_LEVEL, err_code, NULL, 2280 : : + "Checking master binlog format failed with error: %s", 2281 : : + mysql_error(mysql)); 2282 : 0 : + goto network_err; 2283 : : + } 2284 : 0 : + else if (err_code == ER_UNKNOWN_SYSTEM_VARIABLE) 2285 : : + { 2286 : : + /* 2287 : : + The master is older than the slave and does not support 2288 : : + --binlog-storage-engine, so we know it is using the old format. 2289 : : + */ 2290 : 0 : + DBUG_PRINT("info", ("Old master, no --binlog-storage-engine")); 2291 : 0 : + mi->binlog_storage_engine= false; 2292 : : + } 2293 : : + else 2294 : : + { 2295 : : + /* Fatal error */ 2296 : 0 : + errmsg= "The slave I/O thread stops because a fatal error is " 2297 : : + "encountered when it tries to query the value of " 2298 : : + "@@binlog_storage_engine."; 2299 : 0 : + sprintf(err_buff, "%s Error: %s", errmsg, mysql_error(mysql)); 2300 : 0 : + goto err; 2301 : : + } 2302 : : + } 2303 : : + else 2304 : : + { 2305 : 13051 : + mi->binlog_storage_engine= (master_row[0] != NULL); 2306 : 13051 : + DBUG_PRINT("info", ("Master using --binlog-storage-engine: %d", 2307 : : + mi->binlog_storage_engine)); 2308 : : + } 2309 : 13051 : + if (master_res) 2310 : : + { 2311 : 13051 : + mysql_free_result(master_res); 2312 : 13051 : + master_res= NULL; 2313 : : + } 2314 : : + 2315 : : /* Announce MariaDB slave capabilities. */ 2316 : 13051 : DBUG_EXECUTE_IF("simulate_slave_capability_none", goto after_set_capability;); 2317 : : { 6173 : 0 : goto err; 6174 : : } 6175 : 2888 : mi->received_heartbeats++; 6176 : : + /* 6177 : : compare local and event's versions of log_file, log_pos. 6178 : : + 6179 : : Heartbeat is sent only after an event corresponding to the coordinates 6180 : : the heartbeat carries. 6181 : : 6193 : : 6194 : : Slave can have lower coordinates, if some event from master was omitted. 6195 : : 6196 : : + When the master is using new binlog format (--binlog-storage-engine), 6197 : : + then binlog coordinates are not meaningful (GTID is used always), the 6198 : : + slave does not track the master binlog coordinates, and the heartbeat 6199 : : + coordinates should just be ignored. 6200 : : + 6201 : : TODO: handling `when' for SHOW SLAVE STATUS' seconds behind 6202 : : 6203 : : TODO: Extend heartbeat events to use GTIDs instead of binlog 6204 : : coordinates. This would alleviate the strange exceptions during log 6205 : : rotation. 6206 : : */ 6207 : 8603 : + if (!mi->binlog_storage_engine && 6208 : 2827 : + mi->master_log_pos && 6209 : 8540 : !memcmp(mi->master_log_name, hb.get_log_ident(), hb.get_ident_len()) && 6210 : 2825 : mi->master_log_pos > hb.log_pos) 6211 : : { 6778 : 37362 : (uchar)buf[EVENT_TYPE_OFFSET] != STOP_EVENT)) 6779 : : { 6780 : 45092 : mi->master_log_pos+= inc_pos; 6781 : 45092 : + if (!mi->binlog_storage_engine) 6782 : : + { 6783 : 41432 : + memcpy(rli->ign_master_log_name_end, mi->master_log_name, FN_REFLEN); 6784 : 41432 : + DBUG_ASSERT(rli->ign_master_log_name_end[0]); 6785 : 41432 : + rli->ign_master_log_pos_end= mi->master_log_pos; 6786 : : + } 6787 : 45092 : if (got_gtid_event) 6788 : 9586 : rli->ign_gtids.update(&event_gtid); 6789 : : } ===== File: sql/sql_binlog.cc ===== 57 : : { 58 : 16106 : case START_EVENT_V3: 59 : : case FORMAT_DESCRIPTION_EVENT: 60 : : case QUERY_EVENT: 61 : : case TABLE_MAP_EVENT: 62 : : case WRITE_ROWS_EVENT_V1: 68 : : case PRE_GA_WRITE_ROWS_EVENT: 69 : : case PRE_GA_UPDATE_ROWS_EVENT: 70 : : case PRE_GA_DELETE_ROWS_EVENT: 71 : 16106 : + return 0; 72 : : 73 : 0 : default: 74 : : /* 301 : 9722 : else if (bytes_decoded == 0) 302 : 0 : break; // If no bytes where read, the string contained only whitespace 303 : : 304 : : + /* 305 : : + Create a default format description event. 306 : : + This is used to read the real Format_description_log_event, or to read 307 : : + all events if there is none (as happens with --binlog-storage-engine). 308 : : + */ 309 : 10624 : + if (!rli->relay_log.description_event_for_exec && 310 : 902 : + !(rli->relay_log.description_event_for_exec= 311 : 902 : + new Format_description_log_event(4))) 312 : : + { 313 : 0 : + my_error(ER_OUT_OF_RESOURCES, MYF(0)); 314 : 0 : + goto end; 315 : : + } 316 : : + 317 : 9722 : DBUG_ASSERT(bytes_decoded > 0); 318 : 9722 : DBUG_ASSERT(endptr > strptr); 319 : 9722 : coded_len-= endptr - strptr; ===== File: sql/sql_class.h ===== 72 : : (thd)->enter_stage(&stage, __func__, __FILE__, __LINE__) 73 : : 74 : : #include "my_apc.h" 75 : : +#include "rpl_gtid_base.h" 76 : : 77 : : #include "wsrep.h" 78 : : #include "wsrep_on.h" 5791 : : rpl_gtid m_last_commit_gtid; 5792 : : 5793 : : public: 5794 : 1026581 : + const rpl_gtid *get_last_commit_gtid() { return &m_last_commit_gtid; } 5795 : : void set_last_commit_gtid(rpl_gtid >id); 5796 : : 5797 : : ===== File: sql/sql_parse.cc ===== 4066 : 129350 : WSREP_SYNC_WAIT(thd, WSREP_SYNC_WAIT_BEFORE_SHOW); 4067 : 129350 : if (check_global_access(thd, PRIV_STMT_SHOW_BINLOG_EVENTS)) 4068 : 8 : goto error; 4069 : 129342 : + if (mysql_bin_log.start_use_binlog(thd)) 4070 : : + { 4071 : 0 : + my_error(thd->killed_errno(), MYF(0)); 4072 : 0 : + goto error; 4073 : : + } 4074 : 129342 : res = mysql_show_binlog_events(thd); 4075 : 129342 : + mysql_bin_log.end_use_binlog(thd); 4076 : 129342 : break; 4077 : : } 4078 : : #endif ===== File: sql/sql_reload.cc ===== 174 : 8340 : tmp_write_to_binlog= 0; 175 : 8340 : if (mysql_bin_log.is_open()) 176 : : { 177 : 8043 : + MDL_request mdl_request; 178 : 8043 : + MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_START, 179 : : + MDL_EXPLICIT); 180 : 16082 : + if (thd && 181 : 8039 : + thd->mdl_context.acquire_lock(&mdl_request, 182 : 8039 : + thd->variables.lock_wait_timeout)) 183 : 4 : + result= 1; 184 : : + else 185 : : { 186 : 8039 : + if (thd) 187 : 8035 : + thd->backup_commit_lock= &mdl_request; 188 : : + 189 : 8039 : + DYNAMIC_ARRAY *drop_gtid_domain= 190 : 8039 : + (thd && (thd->lex->delete_gtid_domain.elements > 0)) ? 191 : 42 : + &thd->lex->delete_gtid_domain : NULL; 192 : 8039 : + if (mysql_bin_log.flush_binlog(drop_gtid_domain)) 193 : 86 : + *write_to_binlog= -1; 194 : : + 195 : : + /* Note that WSREP(thd) might not be true here e.g. during 196 : : + SST. */ 197 : 8015 : + if (WSREP_ON) 198 : : + { 199 : : + /* Wait for last binlog checkpoint event to be logged. */ 200 : 105 : + mysql_bin_log.wait_for_last_checkpoint_event(); 201 : : + } 202 : 8015 : + if (thd) 203 : : + { 204 : 8011 : + if (mdl_request.ticket) 205 : 8011 : + thd->mdl_context.release_lock(mdl_request.ticket); 206 : 8011 : + thd->backup_commit_lock= 0; 207 : : + } 208 : : } 209 : : } 210 : : } ===== File: sql/sql_repl.cc ===== 130 : : slave_connection_state *until_gtid_state; 131 : : slave_connection_state until_gtid_state_obj; 132 : : Format_description_log_event *fdev; 133 : : + handler_binlog_reader *engine_binlog_reader; 134 : : + /* 135 : : + Last file_no reported as the current point to slave (using a fake rotate 136 : : + event prior to a GTID event, mainly for debugging purposes). 137 : : + */ 138 : : + uint64_t prev_reported_file_no; 139 : : int mariadb_slave_capability; 140 : : enum_gtid_skip_type gtid_skip_group; 141 : : enum_gtid_until_state gtid_until_group; 175 : : char *lfn) 176 : 25406 : : thd(thd_arg), net(&thd_arg->net), packet(packet_arg), 177 : 25406 : log_file_name(lfn), until_gtid_state(NULL), fdev(NULL), 178 : 12703 : + engine_binlog_reader(NULL), prev_reported_file_no(~(uint64_t)0), 179 : 12703 : gtid_skip_group(GTID_SKIP_NOT), gtid_until_group(GTID_UNTIL_NOT_DONE), 180 : 12703 : flags(flags_arg), current_checksum_alg(BINLOG_CHECKSUM_ALG_UNDEF), 181 : 12703 : slave_gtid_strict_mode(false), send_fake_gtid_list(false), 195 : 12703 : bzero(&error_gtid, sizeof(error_gtid)); 196 : 12703 : until_binlog_state.init(); 197 : 12703 : } 198 : 12643 : + ~binlog_send_info() { delete engine_binlog_reader; } 199 : : }; 200 : : 201 : : // prototype 309 : 17952 : } 310 : : 311 : : 312 : 1494 : +static int fake_format_description_event(binlog_send_info *info, 313 : : + Format_description_log_event *fdev, 314 : : + const char **errmsg, 315 : : + uint32 current_pos) 316 : : +{ 317 : : + my_bool do_checksum; 318 : : + int err; 319 : : + ha_checksum crc; 320 : : + char buf[320]; 321 : 1494 : + String str(buf, sizeof(buf), system_charset_info); 322 : 1494 : + String* packet= info->packet; 323 : : + 324 : 1494 : + str.length(0); 325 : 1494 : + fdev->dont_set_created= true; 326 : 1494 : + if (fdev->to_packet(&str)) 327 : : + { 328 : 0 : + info->error= ER_UNKNOWN_ERROR; 329 : 0 : + *errmsg= "Failed due to out-of-memory writing Format_description event"; 330 : 0 : + return -1; 331 : : + } 332 : 1494 : + if ((err= fake_event_header(packet, FORMAT_DESCRIPTION_EVENT, 333 : 1494 : + str.length(), &do_checksum, &crc, 334 : : + errmsg, BINLOG_CHECKSUM_ALG_CRC32, 335 : : + current_pos))) 336 : : + { 337 : 0 : + info->error= ER_UNKNOWN_ERROR; 338 : 0 : + return err; 339 : : + } 340 : : + 341 : 1494 : + packet->append(str); 342 : 1494 : + if (do_checksum) 343 : : + { 344 : 1494 : + crc= my_checksum(crc, (uchar*)str.ptr(), str.length()); 345 : : + } 346 : : + 347 : 2988 : + if ((err= fake_event_footer(packet, do_checksum, crc, errmsg)) || 348 : 1494 : + (err= fake_event_write(info->net, packet, errmsg))) 349 : : + { 350 : 0 : + info->error= ER_UNKNOWN_ERROR; 351 : 0 : + return err; 352 : : + } 353 : : + 354 : 1494 : + return 0; 355 : 1494 : +} 356 : : + 357 : : + 358 : : /* 359 : : Reset thread transmit packet buffer for event sending 360 : : 617 : : we just started reading the index file. In that case 618 : : we have nothing to adjust 619 : : */ 620 : 18 : + if (linfo->index_file_offset >= *purge_offset) 621 : 18 : linfo->index_file_offset-= *purge_offset; 622 : : } 623 : 370 : mysql_mutex_unlock(&thd->LOCK_thd_data); 657 : : 658 : : 659 : : /* 660 : : + Check if a log is in use (legacy binlog). 661 : : 662 : : @return 0 Not used 663 : : @return 1 A slave is reading from the log 679 : : } 680 : : 681 : : 682 : : +struct st_engine_binlog_in_use { 683 : : + uint64_t min_file_no; 684 : : + uint count; 685 : : +}; 686 : : + 687 : : + 688 : : +my_bool 689 : 18398 : +engine_binlog_in_use_callback(THD *thd, st_engine_binlog_in_use *arg) 690 : : +{ 691 : 18398 : + if (thd->current_linfo) 692 : : + { 693 : 464 : + mysql_mutex_lock(&thd->LOCK_thd_data); 694 : 464 : + if (LOG_INFO *linfo= thd->current_linfo) 695 : : + { 696 : 464 : + uint64_t file_no= linfo->file_no.load(std::memory_order_relaxed); 697 : 464 : + if (file_no < arg->min_file_no) 698 : 464 : + arg->min_file_no= file_no; 699 : 464 : + if (file_no != ~(uint64_t)0) 700 : 464 : + ++arg->count; 701 : : + } 702 : 464 : + mysql_mutex_unlock(&thd->LOCK_thd_data); 703 : : + } 704 : 18398 : + return FALSE; 705 : : +} 706 : : + 707 : : + 708 : : +/* 709 : : + Find earliest binlog file in use (--binlog-storage-engine). 710 : : + 711 : : + Returns a pair of the earliest file_no binlog in use by a dump thread, 712 : : + and the number of actively running dump threads. 713 : : +*/ 714 : : +std::pair 715 : 5176 : +engine_binlog_in_use() 716 : : +{ 717 : 5176 : + DBUG_ASSERT(opt_binlog_engine_hton); 718 : 5176 : + st_engine_binlog_in_use arg{~(uint64_t)0, 0}; 719 : 5176 : + server_threads.iterate(engine_binlog_in_use_callback, &arg); 720 : 5176 : + return {arg.min_file_no, arg.count}; 721 : : +} 722 : : + 723 : : + 724 : : +/* 725 : : + Inform engine about server state relevant for automatic binlog purge. 726 : : + Used by engines that implement --binlog-storage-engine. 727 : : + 728 : : + Returns true if automatic purge should proceed with supplied information, 729 : : + false if automatic purge is disabled due to 730 : : + --slave-connections-needed-for-purge. 731 : : +*/ 732 : : +bool 733 : 5086 : +ha_binlog_purge_info(handler_binlog_purge_info *out_info) 734 : : +{ 735 : 5086 : + auto p= engine_binlog_in_use(); 736 : 5086 : + out_info->limit_file_no= p.first; 737 : 5086 : + uint num_dump_threads= p.second; 738 : 5086 : + out_info->purge_by_name= false; 739 : 5086 : + out_info->limit_name= nullptr; 740 : 5086 : + if (binlog_expire_logs_seconds) 741 : : + { 742 : 4 : + out_info->purge_by_date= true; 743 : 4 : + out_info->limit_date= my_time(0) - binlog_expire_logs_seconds; 744 : : + } 745 : : + else 746 : 5082 : + out_info->purge_by_date= false; 747 : 5086 : + if (binlog_space_limit) 748 : : + { 749 : 28 : + out_info->purge_by_size= true; 750 : 28 : + out_info->limit_size= binlog_space_limit; 751 : : + } 752 : : + else 753 : 5058 : + out_info->purge_by_size= false; 754 : : + 755 : 5086 : + out_info->nonpurge_filename[0]= '\0'; 756 : 5086 : + if (num_dump_threads >= slave_connections_needed_for_purge) 757 : : + { 758 : 780 : + out_info->nonpurge_reason= nullptr; 759 : 780 : + return true; 760 : : + } 761 : : + else 762 : : + { 763 : 4306 : + out_info->nonpurge_reason= "less than 'slave_connections_needed_for_purge' " 764 : : + "slaves have processed it"; 765 : 4306 : + return false; 766 : : + } 767 : : +} 768 : : + 769 : : + 770 : 225 : bool purge_error_message(THD* thd, int res) 771 : : { 772 : : uint errcode; 794 : : */ 795 : 230 : bool purge_master_logs(THD* thd, const char* to_log) 796 : : { 797 : 230 : if (!mysql_bin_log.is_open()) 798 : : { 799 : 3 : my_ok(thd); 800 : 3 : return FALSE; 801 : : } 802 : : 803 : 227 : + MDL_request mdl_request; 804 : 227 : + MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_START, 805 : : + MDL_EXPLICIT); 806 : 227 : + if (thd->mdl_context.acquire_lock(&mdl_request, 807 : 227 : + thd->variables.lock_wait_timeout)) 808 : 0 : + return TRUE; 809 : 227 : + thd->backup_commit_lock= &mdl_request; 810 : : + 811 : : + int res; 812 : 227 : + if (!opt_binlog_engine_hton) 813 : : + { 814 : : + char search_file_name[FN_REFLEN]; 815 : 145 : + mysql_bin_log.make_log_name(search_file_name, to_log); 816 : 145 : + res= mysql_bin_log.purge_logs(thd, search_file_name, 0, 1, 1, 1, NULL); 817 : : + } 818 : : + else 819 : : + { 820 : : + handler_binlog_purge_info purge_info; 821 : 82 : + auto p= engine_binlog_in_use(); 822 : 82 : + purge_info.limit_file_no= p.first; 823 : 82 : + uint num_dump_threads= p.second; 824 : 82 : + if (num_dump_threads < slave_connections_needed_for_purge) 825 : : + { 826 : : + /* 827 : : + Prevent purging any file. 828 : : + We need to do it this way, since we have to call into the engine to let 829 : : + it check if there are any files to potentially purge. If there are, we 830 : : + want to give an error that purge was not possible. But if there were no 831 : : + files to purge in any case, we do not want to give any error. 832 : : + */ 833 : 2 : + purge_info.limit_file_no= 0; 834 : 2 : + purge_info.nonpurge_reason= "less than " 835 : : + "'slave_connections_needed_for_purge' slaves have processed it"; 836 : : + } 837 : : + else 838 : 80 : + purge_info.nonpurge_reason= nullptr; 839 : 82 : + purge_info.nonpurge_filename[0]= '\0'; 840 : 82 : + purge_info.purge_by_date= false; 841 : 82 : + purge_info.limit_date= (time_t)0; 842 : 82 : + purge_info.purge_by_size= false; 843 : 82 : + purge_info.limit_size= 0; 844 : 82 : + purge_info.purge_by_name= true; 845 : 82 : + purge_info.limit_name= to_log; 846 : 82 : + res= (*opt_binlog_engine_hton->binlog_purge)(&purge_info); 847 : 82 : + if (res && purge_info.nonpurge_reason) 848 : 24 : + give_purge_note(purge_info.nonpurge_reason, 849 : : + purge_info.nonpurge_filename, true); 850 : : + } 851 : : + 852 : 203 : + if (mdl_request.ticket) 853 : 203 : + thd->mdl_context.release_lock(mdl_request.ticket); 854 : 203 : + thd->backup_commit_lock= 0; 855 : : + 856 : 203 : + return purge_error_message(thd, res); 857 : : } 858 : : 859 : : 875 : 5 : my_ok(thd); 876 : 5 : return 0; 877 : : } 878 : : + int res; 879 : 22 : + if (!opt_binlog_engine_hton) 880 : 20 : + res= mysql_bin_log.purge_logs_before_date(thd, purge_time, 1); 881 : : + else 882 : : + { 883 : : + handler_binlog_purge_info purge_info; 884 : 2 : + auto p= engine_binlog_in_use(); 885 : 2 : + purge_info.limit_file_no= p.first; 886 : 2 : + uint num_dump_threads= p.second; 887 : 2 : + if (num_dump_threads < slave_connections_needed_for_purge) 888 : : + { 889 : 0 : + purge_info.limit_file_no= 0; 890 : 0 : + purge_info.nonpurge_reason= "less than " 891 : : + "'slave_connections_needed_for_purge' slaves have processed it"; 892 : : + } 893 : : + else 894 : 2 : + purge_info.nonpurge_reason= nullptr; 895 : 2 : + purge_info.nonpurge_filename[0]= '\0'; 896 : 2 : + purge_info.purge_by_date= true; 897 : 2 : + purge_info.limit_date= purge_time; 898 : 2 : + purge_info.purge_by_size= false; 899 : 2 : + purge_info.limit_size= 0; 900 : 2 : + purge_info.purge_by_name= false; 901 : 2 : + purge_info.limit_name= nullptr; 902 : 2 : + res= (*opt_binlog_engine_hton->binlog_purge)(&purge_info); 903 : 2 : + if (res && purge_info.nonpurge_reason) 904 : 0 : + give_purge_note(purge_info.nonpurge_reason, 905 : : + purge_info.nonpurge_filename, true); 906 : : + } 907 : 22 : + return purge_error_message(thd, res); 908 : : } 909 : : 910 : 22 : void set_read_error(binlog_send_info *info, int error) 1156 : 2960 : } 1157 : : 1158 : : 1159 : : /** 1160 : : Read all binary logs and return as a list 1161 : : 1180 : 11114 : binlog_file_entry *current_list= NULL, *current_link= NULL, *e; 1181 : 11114 : DBUG_ENTER("get_binlog_list"); 1182 : : 1183 : 11114 : + if (opt_binlog_engine_hton) 1184 : : + { 1185 : 556 : + if (already_locked) 1186 : 556 : + mysql_bin_log.unlock_index(); 1187 : 556 : + DBUG_RETURN((*opt_binlog_engine_hton->get_binlog_file_list)(memroot)); 1188 : : + } 1189 : : + 1190 : 10558 : if (!mysql_bin_log.is_open()) 1191 : : { 1192 : 0 : if (already_locked) 1529 : : } 1530 : : 1531 : : 1532 : : +static const char *gtid_too_old_errmsg= 1533 : : + "Could not find GTID state requested by slave in any binlog " 1534 : : + "files. Probably the slave state is too old and required binlog files " 1535 : : + "have been purged."; 1536 : : + 1537 : : /* 1538 : : Helper function for gtid_find_binlog_pos() below. 1539 : : Check a binlog file against a slave position. Use a GTID index if present. 1627 : : } 1628 : : 1629 : : 1630 : : +/* 1631 : : + Do some checks on each GTID in the starting GTID state found when searching 1632 : : + for the starting GTID position in the binlog. 1633 : : +*/ 1634 : : +static void 1635 : 25827 : +found_pos_check_gtid(const rpl_gtid *found_gtid, slave_connection_state *state, 1636 : : + slave_connection_state *until_gtid_state) 1637 : : +{ 1638 : 25827 : + const rpl_gtid *gtid= state->find(found_gtid->domain_id); 1639 : 25827 : + if (!gtid) 1640 : : + { 1641 : : + /* 1642 : : + Contains_all_slave_gtid() returns false if there is any domain in 1643 : : + Gtid_list_event which is not in the requested slave position. 1644 : : + 1645 : : + We may delete a domain from the slave state inside this loop, but 1646 : : + we only do this when it is the very last GTID logged for that 1647 : : + domain in earlier binlogs, and then we can not encounter it in any 1648 : : + further GTIDs in the Gtid_list. 1649 : : + */ 1650 : 0 : + DBUG_ASSERT(0); 1651 : 25827 : + } else if (gtid->server_id == found_gtid->server_id && 1652 : 18457 : + gtid->seq_no == found_gtid->seq_no) 1653 : : + { 1654 : : + /* 1655 : : + The slave requested to start from the very beginning of this 1656 : : + domain in this binlog file. So delete the entry from the state, 1657 : : + we do not need to skip anything. 1658 : : + */ 1659 : 12817 : + state->remove(gtid); 1660 : : + } 1661 : : + 1662 : 42387 : + if (until_gtid_state && 1663 : 16560 : + (gtid= until_gtid_state->find(found_gtid->domain_id)) && 1664 : 51639 : + gtid->server_id == found_gtid->server_id && 1665 : 9252 : + gtid->seq_no <= found_gtid->seq_no) 1666 : : + { 1667 : : + /* 1668 : : + We've already reached the stop position in UNTIL for this domain, 1669 : : + since it is before the start position. 1670 : : + */ 1671 : 2362 : + until_gtid_state->remove(gtid); 1672 : : + } 1673 : 25827 : +} 1674 : : + 1675 : : + 1676 : : /* 1677 : : Find the name of the binlog file to start reading for a slave that connects 1678 : : using GTID state. 1769 : : their UNTIL condition. 1770 : : */ 1771 : 21514 : for (i= 0; i < count; ++i) 1772 : 11878 : + found_pos_check_gtid(&(gtids[i]), state, until_gtid_state); 1773 : : } 1774 : : 1775 : 9636 : goto end; 1778 : : } 1779 : : 1780 : : /* We reached the end without finding anything. */ 1781 : 18 : + errormsg= gtid_too_old_errmsg; 1782 : : 1783 : 9654 : end: 1784 : 9654 : if (glev) 1792 : : } 1793 : : 1794 : : 1795 : : +static const char * 1796 : 1494 : +gtid_find_engine_pos(binlog_send_info *info) 1797 : : +{ 1798 : 1494 : + handler_binlog_reader *binlog_reader= info->engine_binlog_reader; 1799 : 1494 : + slave_connection_state *pos= &info->gtid_state; 1800 : 1494 : + slave_connection_state *until_gtid_pos= info->until_gtid_state; 1801 : 1494 : + rpl_binlog_state *until_binlog_state= &info->until_binlog_state; 1802 : : + 1803 : 1494 : + int res= binlog_reader->init_gtid_pos(info->thd, pos, until_binlog_state); 1804 : 1494 : + if (res < 0) 1805 : 0 : + return "Error while looking up GTID position in engine binlog"; 1806 : 1494 : + if (res == 0) 1807 : 0 : + return gtid_too_old_errmsg; 1808 : 1494 : + until_binlog_state->iterate( 1809 : 13949 : + [pos, until_gtid_pos] (const rpl_gtid *gtid) -> bool { 1810 : 13949 : + found_pos_check_gtid(gtid, pos, until_gtid_pos); 1811 : 13949 : + return false; 1812 : : + }); 1813 : 1494 : + return nullptr; 1814 : : +} 1815 : : + 1816 : : + 1817 : : static bool 1818 : 6648 : gtid_index_lookup_pos(const char *name, uint32 offset, uint32 *out_start_seek, 1819 : : slave_connection_state *out_gtid_state) 2495 : : 2496 : 2228258 : THD_STAGE_INFO(info->thd, stage_sending_binlog_event_to_slave); 2497 : : 2498 : 2228658 : + if (opt_binlog_engine_hton) 2499 : 360253 : + pos= 4; // ToDo: Support for semi-sync in binlog-in-engine 2500 : : + else 2501 : 1868405 : + pos= my_b_tell(log); 2502 : 2228519 : if (repl_semisync_master.update_sync_header(info->thd, 2503 : 2228641 : (uchar*) packet->c_ptr_safe(), 2504 : 2228651 : info->log_file_name + info->dirlen, 2587 : 12703 : String slave_until_gtid_str(str_buf2, sizeof(str_buf2), system_charset_info); 2588 : 12703 : connect_gtid_state.length(0); 2589 : : 2590 : 14197 : + if (opt_binlog_engine_hton && 2591 : 1494 : + !(info->engine_binlog_reader= 2592 : 1494 : + (*opt_binlog_engine_hton->get_binlog_reader)(true))) 2593 : : + { 2594 : 0 : + LEX_CSTRING *engine_name= hton_name(opt_binlog_engine_hton); 2595 : 0 : + my_error(ER_CANNOT_INIT_ENGINE_BINLOG_READER, MYF(0), engine_name->str); 2596 : 0 : + info->errmsg= "Error while initializing engine binlog reader"; 2597 : 0 : + info->error= ER_CANNOT_INIT_ENGINE_BINLOG_READER; 2598 : 0 : + return 1; 2599 : : + } 2600 : : + 2601 : : /** save start file/pos that was requested by slave */ 2602 : 12703 : strmake(info->start_log_file_name, log_ident, 2603 : : sizeof(info->start_log_file_name)); 2622 : 1386 : info->is_until_before_gtids= get_slave_gtid_until_before_gtids(thd); 2623 : : } 2624 : : } 2625 : 1507 : + else if (opt_binlog_engine_hton) 2626 : : + { 2627 : 0 : + my_error(ER_ENGINE_BINLOG_REQUIRES_GTID, MYF(0)); 2628 : 0 : + info->errmsg= 2629 : : + "Slave must enable GTID mode when master uses --binlog-storage-engine"; 2630 : 0 : + info->error= ER_ENGINE_BINLOG_REQUIRES_GTID; 2631 : 0 : + return 1; 2632 : : + } 2633 : : 2634 : 12703 : DBUG_EXECUTE_IF("binlog_force_reconnect_after_22_events", 2635 : : { 2692 : 36 : info->error= error; 2693 : 36 : return 1; 2694 : : } 2695 : : + 2696 : 11148 : + if (opt_binlog_engine_hton) 2697 : : { 2698 : 1494 : + if ((info->errmsg= gtid_find_engine_pos(info))) 2699 : : + { 2700 : 0 : + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; 2701 : 0 : + return 1; 2702 : : + } 2703 : 1494 : + found_in_index= true; 2704 : 1494 : + start_seek= 0; /* Not used when binlog implemented in engine. */ 2705 : : + } 2706 : : + else 2707 : : + { 2708 : 9654 : + if ((info->errmsg= gtid_find_binlog_pos(&info->gtid_state, 2709 : : + search_file_name, 2710 : : + info->until_gtid_state, 2711 : : + &info->until_binlog_state, 2712 : : + &found_in_index, &start_seek))) 2713 : : + { 2714 : 18 : + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; 2715 : 18 : + return 1; 2716 : : + } 2717 : : } 2718 : : 2719 : 11130 : if (found_in_index) 2736 : : } 2737 : 12637 : linfo->index_file_offset= 0; 2738 : : 2739 : 23780 : + if (!opt_binlog_engine_hton && 2740 : 11143 : + mysql_bin_log.find_log_pos(linfo, name, 1)) 2741 : : { 2742 : 6 : info->errmsg= "Could not find first log file name in binary " 2743 : : "log index file"; 2750 : : // note: publish that we use file, before we open it 2751 : 12631 : thd->set_current_linfo(linfo); 2752 : : 2753 : 23768 : + if (!opt_binlog_engine_hton && 2754 : 11137 : + check_start_offset(info, linfo->log_file_name, *pos)) 2755 : 2 : return 1; 2756 : : 2757 : 12629 : if (*pos > BIN_LOG_HEADER_SIZE) 3185 : 8096 : return 0; 3186 : : } 3187 : : 3188 : : + 3189 : : +/* 3190 : : + Helper function for send_events() and send_engine_events(). 3191 : : + After an event has been sent to the client, it handles sending a fake 3192 : : + GTID_LIST event if needed; and it handles checking the GTID until stop 3193 : : + condition, if any. 3194 : : +*/ 3195 : : +static bool 3196 : 2837400 : +send_event_gtid_list_and_until(binlog_send_info *info, ulong *ev_offset, 3197 : : + Log_event_type event_type, my_off_t log_pos) 3198 : : +{ 3199 : 2837400 : + if (unlikely(info->send_fake_gtid_list) && 3200 : 38725 : + info->gtid_skip_group == GTID_SKIP_NOT) 3201 : : + { 3202 : 10521 : + Gtid_list_log_event glev(&info->until_binlog_state, 0); 3203 : : + 3204 : 21042 : + if (reset_transmit_packet(info, info->flags, ev_offset, &info->errmsg) || 3205 : 10521 : + fake_gtid_list_event(info, &glev, &info->errmsg, (uint32)log_pos)) 3206 : : + { 3207 : 0 : + info->error= ER_UNKNOWN_ERROR; 3208 : 0 : + return true; 3209 : : + } 3210 : 10521 : + info->send_fake_gtid_list= false; 3211 : 10521 : + } 3212 : : + 3213 : 3412505 : + if (info->until_gtid_state && 3214 : 575104 : + is_until_reached(info, ev_offset, event_type, &info->errmsg, 3215 : : + (uint32)log_pos)) 3216 : : + { 3217 : 1358 : + if (info->errmsg) 3218 : : + { 3219 : 0 : + info->error= ER_UNKNOWN_ERROR; 3220 : 0 : + return true; 3221 : : + } 3222 : 1358 : + info->should_stop= true; 3223 : : + } 3224 : : + 3225 : 2837401 : + return false; 3226 : : +} 3227 : : + 3228 : : + 3229 : : /** 3230 : : * This function sends events from one binlog file 3231 : : * but only up until end_pos 3240 : : ulong ev_offset; 3241 : : 3242 : 260512 : String *packet= info->packet; 3243 : 260512 : + DBUG_ASSERT(!info->engine_binlog_reader); 3244 : 260512 : linfo->pos= my_b_tell(log); 3245 : 260514 : info->last_pos= my_b_tell(log); 3246 : : 3314 : : ev_offset, &info->error_gtid)))) 3315 : 52 : return 1; 3316 : : 3317 : 2139886 : + if (send_event_gtid_list_and_until(info, &ev_offset, event_type, 3318 : : + my_b_tell(log))) 3319 : 0 : + return 1; 3320 : : + 3321 : : + /* Abort server before it sends the XID_EVENT */ 3322 : 2139806 : + DBUG_EXECUTE_IF("crash_before_send_xid", 3323 : : + { 3324 : : + if (event_type == XID_EVENT) 3325 : : + { 3326 : : + my_sleep(2000000); 3327 : : + DBUG_SUICIDE(); 3328 : : + } 3329 : : + }); 3330 : : + } 3331 : : + 3332 : 259600 : + return 0; 3333 : : +} 3334 : : + 3335 : : + 3336 : : +/** 3337 : : + * Send events from binlog implemented in storage engine. Will wait for more 3338 : : + * data to become available as needed. 3339 : : + * 3340 : : + * return 0 - OK 3341 : : + * else NOK 3342 : : + */ 3343 : 1494 : +static int send_engine_events(binlog_send_info *info, LOG_INFO* linfo) 3344 : : +{ 3345 : : + int error; 3346 : : + ulong ev_offset; 3347 : : + 3348 : 1494 : + String *packet= info->packet; 3349 : 1494 : + handler_binlog_reader *reader= info->engine_binlog_reader; 3350 : 1494 : + DBUG_ASSERT(reader); 3351 : 707992 : + while (!should_stop(info)) 3352 : : + { 3353 : : + /* reset the transmit packet for the event read from binary log 3354 : : + file */ 3355 : 706833 : + if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg)) 3356 : 0 : + return 1; 3357 : : + 3358 : 706833 : + error= reader->read_log_event(packet, packet->length(), 3359 : 706833 : + info->thd->variables.max_allowed_packet); 3360 : 706833 : + if (unlikely(error) && error != LOG_READ_EOF) 3361 : : { 3362 : 0 : + set_read_error(info, error); 3363 : 0 : + return 1; 3364 : : + } 3365 : : 3366 : 706833 : + uint64_t prev_file_no= linfo->file_no.load(std::memory_order_relaxed); 3367 : 706833 : + if (unlikely(prev_file_no == ~(uint64_t)0) || 3368 : 705339 : + unlikely(reader->cur_file_no > prev_file_no)) 3369 : : + { 3370 : 2141 : + linfo->file_no.store(reader->cur_file_no, std::memory_order_relaxed); 3371 : 2141 : + (*opt_binlog_engine_hton->get_filename)(info->log_file_name, 3372 : : + reader->cur_file_no); 3373 : : } 3374 : 706833 : + linfo->pos= (my_off_t) reader->cur_file_pos; 3375 : : 3376 : 706833 : + if (error == LOG_READ_EOF) 3377 : : { 3378 : : + /** 3379 : : + * check if we should wait for more data 3380 : : + */ 3381 : 9261 : + if ((info->flags & BINLOG_DUMP_NON_BLOCK) || 3382 : 9251 : + (info->thd->variables.server_id == 0)) 3383 : : + { 3384 : 10 : + info->should_stop= true; 3385 : 10 : + return 0; 3386 : : + } 3387 : : + 3388 : : + /** 3389 : : + * flush data before waiting 3390 : : + */ 3391 : 9251 : + if (net_flush(info->net)) 3392 : : { 3393 : 322 : + info->errmsg= "failed on net_flush()"; 3394 : 322 : info->error= ER_UNKNOWN_ERROR; 3395 : 322 : return 1; 3396 : : } 3397 : : + 3398 : 17906 : + while (!should_stop(info, true) && !reader->data_available()) 3399 : : + { 3400 : : + struct timespec ts; 3401 : 8980 : + struct timespec *ts_ptr= nullptr; 3402 : 8980 : + if (info->heartbeat_period) 3403 : : + { 3404 : 8980 : + set_timespec_nsec(ts, info->heartbeat_period); 3405 : 8980 : + ts_ptr= &ts; 3406 : : + } 3407 : 8980 : + bool ret= reader->wait_available(info->thd, ts_ptr); 3408 : 8978 : + if (info->heartbeat_period && ret) 3409 : : + { 3410 : : + struct event_coordinates coord= 3411 : 63 : + { info->log_file_name, reader->cur_file_pos }; 3412 : 63 : + int err= send_heartbeat_event(info, info->net, info->packet, &coord, 3413 : : + BINLOG_CHECKSUM_ALG_OFF); 3414 : 63 : + if (err) 3415 : 1 : + return 1; 3416 : 62 : + info->heartbeat_period= get_heartbeat_period(info->thd); 3417 : : + } 3418 : : + } 3419 : 8926 : + continue; 3420 : 8926 : } 3421 : : 3422 : : + Log_event_type event_type= 3423 : 697572 : + (Log_event_type)((uchar)(*packet)[EVENT_TYPE_OFFSET+ev_offset]); 3424 : : + 3425 : 697572 : + DBUG_ASSERT(event_type != START_ENCRYPTION_EVENT); 3426 : : +#ifdef ENABLED_DEBUG_SYNC 3427 : 697572 : + DBUG_EXECUTE_IF("dump_thread_wait_before_send_xid", 3428 : : { 3429 : : if (event_type == XID_EVENT) 3430 : : { 3431 : : + net_flush(info->net); 3432 : : + const char act[]= 3433 : : + "now " 3434 : : + "wait_for signal.continue"; 3435 : : + DBUG_ASSERT(debug_sync_service); 3436 : : + DBUG_ASSERT(!debug_sync_set_action( 3437 : : + info->thd, 3438 : : + STRING_WITH_LEN(act))); 3439 : : + 3440 : : + const char act2[]= 3441 : : + "now " 3442 : : + "signal signal.continued"; 3443 : : + DBUG_ASSERT(!debug_sync_set_action( 3444 : : + info->thd, 3445 : : + STRING_WITH_LEN(act2))); 3446 : : } 3447 : : }); 3448 : : +#endif 3449 : : + 3450 : 697572 : + if (event_type == GTID_EVENT && prev_file_no != info->prev_reported_file_no) 3451 : : + { 3452 : 1512 : + String saved_gtid_packet; 3453 : 1512 : + saved_gtid_packet.swap(*info->packet); 3454 : : + int err= 3455 : 1512 : + fake_rotate_event(info, 0, &info->errmsg, BINLOG_CHECKSUM_ALG_OFF); 3456 : 1512 : + info->prev_reported_file_no= prev_file_no; 3457 : 1512 : + saved_gtid_packet.swap(*info->packet); 3458 : 1512 : + if (err) 3459 : 0 : + return 1; 3460 : 3024 : + } 3461 : 696089 : + else if (unlikely(event_type == FORMAT_DESCRIPTION_EVENT) && 3462 : 29 : + info->gtid_state.count() > 0) 3463 : : + { 3464 : : + /* 3465 : : + In the engine-implemented binlog, format description event is (only) 3466 : : + written to mark a master server restart; this is used by the slave to 3467 : : + know that the master discarded temporary tabls at this point. So don't 3468 : : + send such event until we have reached our GTID starting position, so 3469 : : + that the slave will not mistakenly discard such temporary tables too 3470 : : + early. 3471 : : + */ 3472 : 4 : + continue; 3473 : : + } 3474 : 697568 : + if (((info->errmsg= send_event_to_slave(info, event_type, nullptr, 3475 : : + ev_offset, &info->error_gtid)))) 3476 : 0 : + return 1; 3477 : : + 3478 : 697568 : + if (send_event_gtid_list_and_until(info, &ev_offset, event_type, 0)) 3479 : 0 : + return 1; 3480 : : } 3481 : : 3482 : 1159 : return 0; 3483 : : } 3484 : : 3485 : : + 3486 : : /** 3487 : : * This function sends one binlog file to slave 3488 : : * 3498 : 15212 : mysql_mutex_assert_not_owner(mysql_bin_log.get_log_lock()); 3499 : : 3500 : : /* seek to the requested position, to start the requested dump */ 3501 : 15212 : + if (!opt_binlog_engine_hton && 3502 : : + start_pos != BIN_LOG_HEADER_SIZE) 3503 : : { 3504 : 10429 : my_b_seek(log, start_pos); 3505 : 10429 : linfo->pos= start_pos; 3509 : 15212 : sending_new_binlog_file++; 3510 : 276731 : while (!should_stop(info)) 3511 : : { 3512 : 274710 : + if (opt_binlog_engine_hton) 3513 : : { 3514 : 1494 : + info->dirlen= 0; 3515 : 1494 : + if (send_engine_events(info, linfo)) 3516 : 323 : + return 1; 3517 : : + } 3518 : : + else 3519 : : + { 3520 : : + /** 3521 : : + * get end pos of current log file, this function 3522 : : + * will wait if there is nothing available 3523 : : + */ 3524 : 273216 : + my_off_t end_pos= get_binlog_end_pos(info, log, linfo); 3525 : 273270 : + if (end_pos <= 1) 3526 : : + { 3527 : : + /** end of file or error */ 3528 : 12757 : + return (int)end_pos; 3529 : : + } 3530 : 260513 : + info->dirlen= dirname_length(info->log_file_name); 3531 : : + /** 3532 : : + * send events from current position up to end_pos 3533 : : + */ 3534 : 260514 : + if (send_events(info, log, linfo, end_pos)) 3535 : 80 : + return 1; 3536 : : } 3537 : : } 3538 : : 3539 : 2029 : return 1; 3552 : 12703 : binlog_send_info infoobj(thd, packet, flags, linfo.log_file_name); 3553 : 12703 : binlog_send_info *info= &infoobj; 3554 : 12703 : bool has_transmit_started= false; 3555 : 12703 : + bool start_use_binlog= false; 3556 : : 3557 : 12703 : int old_max_allowed_packet= thd->variables.max_allowed_packet; 3558 : 12703 : thd->variables.max_allowed_packet= MAX_MAX_ALLOWED_PACKET; 3562 : : 3563 : 12703 : bzero((char*) &log,sizeof(log)); 3564 : : 3565 : 12703 : + if (mysql_bin_log.start_use_binlog(thd)) 3566 : : + { 3567 : 0 : + info->errmsg= "Binlog dump terminated by user kill"; 3568 : 0 : + info->error= ER_CONNECTION_KILLED; 3569 : 0 : + goto err; 3570 : : + } 3571 : 12703 : + start_use_binlog= true; 3572 : : + 3573 : 12703 : if (init_binlog_sender(info, &linfo, log_ident, &pos)) 3574 : 74 : goto err; 3575 : : 3606 : : 3607 : 15220 : while (!should_stop(info)) 3608 : : { 3609 : 15220 : + if (opt_binlog_engine_hton) { 3610 : : + /* Build a legacy Format_description event for slave. */ 3611 : 1494 : + if (!(info->fdev= new Format_description_log_event 3612 : 1494 : + (4, 0, BINLOG_CHECKSUM_ALG_OFF))) 3613 : : + { 3614 : 0 : + info->errmsg= "Out of memory initializing format_description event"; 3615 : 0 : + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; 3616 : 0 : + goto err; 3617 : : + } 3618 : 2988 : + if (reset_transmit_packet(info, info->flags, &ev_offset, &info->errmsg) || 3619 : 1494 : + fake_format_description_event(info, info->fdev, &info->errmsg, 3620 : : + (uint32_t)pos)) 3621 : : + { 3622 : 0 : + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; 3623 : 0 : + goto err; 3624 : : + } 3625 : : + } 3626 : : + else /* !opt_binlog_engine_hton */ 3627 : : { 3628 : : /* 3629 : : + Tell the client about the log name with a fake Rotate event; 3630 : : + this is needed even if we also send a Format_description_log_event 3631 : : + just after, because that event does not contain the binlog's name. 3632 : : + Note that as this Rotate event is sent before 3633 : : + Format_description_log_event, the slave cannot have any info to 3634 : : + understand this event's format, so the header len of 3635 : : + Rotate_log_event is FROZEN (so in 5.0 it will have a header shorter 3636 : : + than other events except FORMAT_DESCRIPTION_EVENT). 3637 : : + Before 4.0.14 we called fake_rotate_event below only if (pos == 3638 : : + BIN_LOG_HEADER_SIZE), because if this is false then the slave 3639 : : + already knows the binlog's name. 3640 : : + Since, we always call fake_rotate_event; if the slave already knew 3641 : : + the log's name (ex: CHANGE MASTER TO MASTER_LOG_FILE=...) this is 3642 : : + useless but does not harm much. It is nice for 3.23 (>=.58) slaves 3643 : : + which test Rotate events to see if the master is 4.0 (then they 3644 : : + choose to stop because they can't replicate 4.0); by always calling 3645 : : + fake_rotate_event we are sure that 3.23.58 and newer will detect the 3646 : : + problem as soon as replication starts (BUG#198). 3647 : : + Always calling fake_rotate_event makes sending of normal 3648 : : + (=from-binlog) Rotate events a priori unneeded, but it is not so 3649 : : + simple: the 2 Rotate events are not equivalent, the normal one is 3650 : : + before the Stop event, the fake one is after. If we don't send the 3651 : : + normal one, then the Stop event will be interpreted (by existing 4.0 3652 : : + slaves) as "the master stopped", which is wrong. So for safety, 3653 : : + given that we want minimum modification of 4.0, we send the normal 3654 : : + and fake Rotates. 3655 : : */ 3656 : 13726 : + if (fake_rotate_event(info, pos, &info->errmsg, info->current_checksum_alg)) 3657 : : + { 3658 : : + /* 3659 : : + This error code is not perfect, as fake_rotate_event() does not 3660 : : + read anything from the binlog; if it fails it's because of an 3661 : : + error in my_net_write(), fortunately it will say so in errmsg. 3662 : : + */ 3663 : 0 : + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; 3664 : 0 : + goto err; 3665 : : + } 3666 : : 3667 : 13726 : + if ((file=open_binlog(&log, linfo.log_file_name, &info->errmsg)) < 0) 3668 : : + { 3669 : 0 : + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; 3670 : 0 : + goto err; 3671 : : + } 3672 : : 3673 : 13726 : + if (send_format_descriptor_event(info, &log, &linfo, pos)) 3674 : : + { 3675 : 8 : + info->error= ER_MASTER_FATAL_ERROR_READING_BINLOG; 3676 : 8 : + goto err; 3677 : : + } 3678 : : + 3679 : : + } /* !opt_binlog_engine_hton */ 3680 : : 3681 : : /* 3682 : : We want to corrupt the first event that will be sent to the slave. 3790 : 12645 : thd->variables.max_allowed_packet= old_max_allowed_packet; 3791 : 12645 : delete info->fdev; 3792 : : 3793 : 12643 : + if (start_use_binlog) 3794 : 12643 : + mysql_bin_log.end_use_binlog(thd); 3795 : : + 3796 : 12645 : if (likely(info->error == 0)) 3797 : : { 3798 : 7330 : my_eof(thd); 4908 : : } 4909 : : #endif /* WITH_WSREP */ 4910 : 13601 : bool ret= 0; 4911 : : + 4912 : 13601 : + MDL_request mdl_request; 4913 : 13601 : + MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_START, 4914 : : + MDL_EXPLICIT); 4915 : 13601 : + if (thd->mdl_context.acquire_lock(&mdl_request, 4916 : 13601 : + thd->variables.lock_wait_timeout)) 4917 : 0 : + return 1; 4918 : 13601 : + thd->backup_commit_lock= &mdl_request; 4919 : : + 4920 : : /* Temporarily disable master semisync before resetting master. */ 4921 : 13601 : repl_semisync_master.before_reset_master(); 4922 : 13601 : ret= mysql_bin_log.reset_logs(thd, 1, init_state, init_state_len, 4923 : : next_log_number); 4924 : 13601 : repl_semisync_master.after_reset_master(); 4925 : : + 4926 : 13601 : + if (mdl_request.ticket) 4927 : 13601 : + thd->mdl_context.release_lock(mdl_request.ticket); 4928 : 13601 : + thd->backup_commit_lock= 0; 4929 : : + 4930 : 13601 : DBUG_EXECUTE_IF("crash_after_reset_master", DBUG_SUICIDE();); 4931 : : 4932 : 13601 : return ret; 4933 : : } 4934 : : 4935 : : 4936 : : +/* Version of mysql_show_binlog_events() for --binlog-storage-engine. */ 4937 : : +static bool 4938 : 83 : +show_engine_binlog_events(THD* thd, Protocol *protocol, LEX_MASTER_INFO *lex_mi) 4939 : : +{ 4940 : 83 : + bool err= false; 4941 : : + 4942 : 83 : + DBUG_ASSERT(opt_binlog_engine_hton); 4943 : 83 : + DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS); 4944 : : + handler_binlog_reader *reader= 4945 : 83 : + (*opt_binlog_engine_hton->get_binlog_reader)(false); 4946 : 83 : + if (!reader) 4947 : : + { 4948 : 0 : + my_error(ER_OUT_OF_RESOURCES, MYF(0)); 4949 : 0 : + return true; 4950 : : + } 4951 : : + 4952 : 83 : + ulonglong pos= lex_mi->pos; 4953 : : + /* 4954 : : + The positions "0" and "4" are unfortunately traditionally used 4955 : : + interchangeably to mean "the start of the binlog". Thus, we might here 4956 : : + easily see a starting position of "4", which is probably not valid in 4957 : : + the engine, but which really means "start of the file". 4958 : : + 4959 : : + So here we have this ugly hack where "4" means the same as "0". Well, 4960 : : + use of offsets is discourated anyway in the new binlog (in favour of 4961 : : + GTID), and "4" is not going to be a valid position most likely, or if 4962 : : + it is, "0" will be equivalent (at least it is so for the InnoDB binlog 4963 : : + implementation. 4964 : : + */ 4965 : 83 : + if (pos == 4) 4966 : 35 : + pos= 0; 4967 : 83 : + if (reader->init_legacy_pos(thd, lex_mi->log_file_name, pos)) 4968 : : + { 4969 : 12 : + err= true; 4970 : 12 : + goto end; 4971 : : + } 4972 : : + /* The engine reader will stop at the end of the requested file. */ 4973 : 71 : + reader->enable_single_file(); 4974 : : + 4975 : : + { 4976 : 71 : + SELECT_LEX_UNIT *unit= &thd->lex->unit; 4977 : 71 : + unit->set_limit(thd->lex->current_select); 4978 : 71 : + uint64_t file_no= reader->cur_file_no; 4979 : 71 : + ha_rows limit= unit->lim.get_select_limit(); 4980 : 71 : + String packet; 4981 : 71 : + Format_description_log_event fd(4); 4982 : : + char name_buf[FN_REFLEN]; 4983 : 71 : + opt_binlog_engine_hton->get_filename(name_buf, file_no); 4984 : : + 4985 : 625 : + for (ha_rows event_count= 0; event_count < limit; ++event_count) 4986 : : + { 4987 : 597 : + packet.length(0); 4988 : 1791 : + int reader_error= reader->read_log_event(&packet, 0, 4989 : 597 : + thd->variables.max_allowed_packet + MAX_LOG_EVENT_HEADER); 4990 : 597 : + if (reader_error) 4991 : : + { 4992 : 43 : + if (reader_error != LOG_READ_EOF) 4993 : : + { 4994 : 0 : + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), 4995 : : + "SHOW BINLOG EVENTS", "error reading event data"); 4996 : 0 : + err= true; 4997 : : + } 4998 : 43 : + break; 4999 : : + } 5000 : : + 5001 : 554 : + if (unit->lim.check_offset(event_count)) 5002 : 4 : + continue; 5003 : : + const char *errmsg; 5004 : 550 : + Log_event *ev= Log_event::read_log_event((const uchar *)packet.ptr(), 5005 : 550 : + (uint)packet.length(), 5006 : : + &errmsg, &fd, false, false); 5007 : 550 : + if (!ev) 5008 : : + { 5009 : 0 : + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), 5010 : : + "SHOW BINLOG EVENTS", errmsg); 5011 : 0 : + err= true; 5012 : 0 : + break; 5013 : : + } 5014 : 550 : + int send_err= ev->net_send(protocol, name_buf, 0); 5015 : 550 : + delete ev; 5016 : 550 : + if (send_err) 5017 : : + { 5018 : 0 : + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), 5019 : : + "SHOW BINLOG EVENTS", "Net error"); 5020 : 0 : + err= true; 5021 : 0 : + break; 5022 : : + } 5023 : : + } 5024 : 71 : + } 5025 : : + 5026 : 83 : +end: 5027 : 83 : + if (!err) 5028 : 71 : + my_eof(thd); 5029 : 83 : + delete reader; 5030 : 83 : + return err; 5031 : : +} 5032 : : + 5033 : : + 5034 : : /** 5035 : : Execute a SHOW BINLOG EVENTS statement. 5036 : : 5070 : : Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF)) 5071 : 0 : DBUG_RETURN(TRUE); 5072 : : 5073 : 129519 : + if (opt_binlog_engine_hton && 5074 : 83 : + thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS) 5075 : 83 : + DBUG_RETURN(show_engine_binlog_events(thd, protocol, lex_mi)); 5076 : : + 5077 : 129436 : DBUG_ASSERT(thd->lex->sql_command == SQLCOM_SHOW_BINLOG_EVENTS || 5078 : : thd->lex->sql_command == SQLCOM_SHOW_RELAYLOG_EVENTS); 5079 : : 5369 : 95255 : if (mysql_bin_log.is_open()) 5370 : : { 5371 : 95147 : LOG_INFO li; 5372 : : + char buf[FN_REFLEN]; 5373 : : + const char *base; 5374 : : + uint64_t pos; 5375 : 95147 : + if (opt_binlog_engine_hton) 5376 : : + { 5377 : : + uint64_t file_no; 5378 : 20130 : + mysql_mutex_lock(mysql_bin_log.get_log_lock()); 5379 : 20130 : + (*opt_binlog_engine_hton->binlog_status)(&file_no, &pos); 5380 : 20130 : + (*opt_binlog_engine_hton->get_filename)(buf, file_no); 5381 : 20130 : + mysql_mutex_unlock(mysql_bin_log.get_log_lock()); 5382 : 20130 : + base= buf; 5383 : : + } 5384 : : + else 5385 : : + { 5386 : 75017 : + mysql_bin_log.get_current_log(&li); 5387 : 75017 : + pos= (uint64_t) li.pos; 5388 : 75017 : + size_t dir_len = dirname_length(li.log_file_name); 5389 : 75017 : + base= li.log_file_name + dir_len; 5390 : : + } 5391 : : 5392 : 95147 : protocol->store(base, strlen(base), &my_charset_bin); 5393 : 95147 : + protocol->store((ulonglong)pos); 5394 : 95147 : protocol->store(binlog_filter->get_do_db()); 5395 : 95147 : protocol->store(binlog_filter->get_ignore_db()); 5396 : 95147 : if (protocol->write()) 5487 : 5345 : cur_link->name.str+= dir_len; 5488 : 5345 : cur_link->name.length-= dir_len; 5489 : : 5490 : 8243 : + if (!opt_binlog_engine_hton && 5491 : 2898 : + mysql_bin_log.get_reset_master_count() > expected_reset_masters) 5492 : : { 5493 : : /* 5494 : : Reset master was called after we cached filenames. 5498 : 2 : goto retry; 5499 : : } 5500 : : 5501 : 5343 : + if (!opt_binlog_engine_hton && 5502 : 2896 : + !(strncmp(fname+dir_len, cur.log_file_name+cur_dir_len, length))) 5503 : 904 : cur_link->size= cur.pos; /* The active log, use the active position */ 5504 : : else 5505 : : { 5820 : 158174 : int res= 1; 5821 : 158174 : const char *ext1_str= strrchr(log_1, '.'); 5822 : 158174 : const char *ext2_str= strrchr(log_2, '.'); 5823 : 158174 : + if (!ext1_str || !ext2_str) 5824 : 1 : + return strcmp(log_1, log_2); 5825 : : char file_name_1[255], file_name_2[255]; 5826 : 158173 : strmake(file_name_1, log_1, (ext1_str - log_1)); 5827 : 158173 : strmake(file_name_2, log_2, (ext2_str - log_2)); ===== File: sql/sql_repl.h ===== 39 : : bool purge_master_logs(THD* thd, const char* to_log); 40 : : bool purge_master_logs_before_date(THD* thd, time_t purge_time); 41 : : int log_in_use(const char* log_name, uint min_connections); 42 : : +std::pair engine_binlog_in_use(); 43 : : void adjust_linfo_offsets(my_off_t purge_offset); 44 : : void show_binlogs_get_fields(THD *thd, List *field_list); 45 : : bool show_binlogs(THD* thd); ===== File: sql/sys_vars.cc ===== 1315 : : 1316 : 64 : if (opt_bin_log) 1317 : : { 1318 : 56 : + if (opt_binlog_engine_hton) 1319 : : + { 1320 : 44 : + if (loc_binlog_space_limit) 1321 : 6 : + mysql_bin_log.engine_purge_logs_by_size(loc_binlog_space_limit); 1322 : : + } 1323 : : + else 1324 : : + { 1325 : 12 : + if (loc_binlog_space_limit) 1326 : 12 : + mysql_bin_log.count_binlog_space(); 1327 : : + /* Inform can_purge_log() that it should do a recheck of log_in_use() */ 1328 : 12 : + sending_new_binlog_file++; 1329 : 12 : + mysql_bin_log.unlock_index(); 1330 : 12 : + mysql_bin_log.purge(1); 1331 : 12 : + mysql_mutex_lock(&LOCK_global_system_variables); 1332 : 12 : + return 0; 1333 : : + } 1334 : : } 1335 : 52 : mysql_bin_log.unlock_index(); 1336 : 52 : mysql_mutex_lock(&LOCK_global_system_variables); 1883 : : BLOCK_SIZE(IO_SIZE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(0), 1884 : : ON_UPDATE(fix_max_binlog_size)); 1885 : : 1886 : : + 1887 : : +static Sys_var_charptr_fscs Sys_binlog_directory( 1888 : : + "binlog_directory", 1889 : : + "Directory path (absolute or relative to datadir) where binlog files " 1890 : : + "are stored. If this is used, must not specify a directory path for " 1891 : : + "--log-bin", 1892 : : + READ_ONLY GLOBAL_VAR(opt_binlog_directory), CMD_LINE(REQUIRED_ARG), 1893 : : + DEFAULT(0)); 1894 : : + 1895 : : + 1896 : 1262 : static bool fix_max_connections(sys_var *self, THD *thd, enum_var_type type) 1897 : : { 1898 : 1262 : return false; 2412 : 162110 : bool first= true; 2413 : : 2414 : 162110 : str.length(0); 2415 : 162110 : + rpl_gtid gtid= *thd->get_last_commit_gtid(); 2416 : 485010 : if ((gtid.seq_no > 0 && 2417 : 324220 : rpl_slave_state_tostring_helper(&str, >id, &first)) || 2418 : 162110 : !(p= thd->strmake(str.ptr(), str.length()))) 2715 : : GLOBAL_VAR(slave_abort_blocking_timeout), CMD_LINE(REQUIRED_ARG), 2716 : : VALID_RANGE(0, LONG_TIMEOUT), DEFAULT(LONG_TIMEOUT), NO_MUTEX_GUARD, 2717 : : NOT_IN_BINLOG); 2718 : : + 2719 : : + 2720 : : +static Sys_var_charptr_fscs Sys_binlog_storage_engine( 2721 : : + "binlog_storage_engine", 2722 : : + "Use a more efficient binlog implementation integrated with the " 2723 : : + "storage engine. Only available for supporting engines", 2724 : : + READ_ONLY GLOBAL_VAR(opt_binlog_storage_engine), CMD_LINE(REQUIRED_ARG), 2725 : : + DEFAULT(0)); 2726 : : #endif 2727 : : 2728 : : 3835 : : 3836 : : /* new options for semisync */ 3837 : : 3838 : : +static bool 3839 : 803 : +check_rpl_semi_sync_master_enabled(sys_var *self, THD *thd, set_var *var) 3840 : : +{ 3841 : 803 : + if (opt_binlog_engine_hton && var->save_result.ulonglong_value) 3842 : : + { 3843 : 2 : + my_error(ER_NOT_YET_SUPPORTED_ENGINE_BINLOG, MYF(0), 3844 : : + "Semi-synchronous replication"); 3845 : 2 : + return true; 3846 : : + } 3847 : 801 : + return false; 3848 : : +} 3849 : : + 3850 : 806 : static bool fix_rpl_semi_sync_master_enabled(sys_var *self, THD *thd, 3851 : : enum_var_type type) 3852 : : { 3901 : : "Enable semi-synchronous replication master (disabled by default)", 3902 : : GLOBAL_VAR(rpl_semi_sync_master_enabled), 3903 : : CMD_LINE(OPT_ARG), DEFAULT(FALSE), 3904 : : + NO_MUTEX_GUARD, NOT_IN_BINLOG, 3905 : : + ON_CHECK(check_rpl_semi_sync_master_enabled), 3906 : : ON_UPDATE(fix_rpl_semi_sync_master_enabled)); 3907 : : 3908 : : static Sys_var_on_access_globaltransaction->savepoints; 782 : 89 : + while (p) 783 : : + { 784 : 89 : + if (ha_release_savepoint(thd, p)) 785 : 0 : + res= TRUE; 786 : 89 : + if (p == sv) 787 : : + { 788 : 78 : + thd->transaction->savepoints= sv->prev; 789 : 78 : + DBUG_RETURN(MY_TEST(res)); 790 : : + } 791 : 11 : + p= p->prev; 792 : : + } 793 : : 794 : 0 : + DBUG_ASSERT(0 /* Should not get here, would imply that the list of savepoints 795 : : + changed since find_savepoint() called at the start of this 796 : : + function. */); 797 : 0 : + thd->transaction->savepoints= NULL;; 798 : 0 : DBUG_RETURN(MY_TEST(res)); 799 : 93 : } 800 : : ===== File: sql/wsrep_mysqld.cc ===== 2308 : 68 : if (seqno) 2309 : : { 2310 : : Gtid_log_event gtid_event(thd, seqno, domain_id, true, 2311 : : + Log_event::EVENT_NO_CACHE, 2312 : : + LOG_EVENT_SUPPRESS_USE_F, true, 0, 2313 : 68 : + false, false); 2314 : 68 : gtid_event.server_id= server_id; 2315 : 68 : if (!gtid_event.is_valid()) ret= 0; 2316 : 68 : ret= writer.write(>id_event); ===== File: sql/wsrep_thd.cc ===== 464 : 15 : DEBUG_SYNC(thd, "wsrep_kill_before_awake_no_mutex"); 465 : 15 : victim_thd->wsrep_abort_by_kill= kill_signal; 466 : 15 : victim_thd->awake_no_mutex(kill_signal); 467 : 15 : ha_abort_transaction(thd, victim_thd, 1); 468 : 15 : DBUG_RETURN(0); 469 : 17 : } ===== File: storage/innobase/CMakeLists.txt ===== 165 : : fsp/fsp0file.cc 166 : : fsp/fsp0space.cc 167 : : fsp/fsp0sysspace.cc 168 : : + fsp/fsp_binlog.cc 169 : : fut/fut0lst.cc 170 : : ha/ha0storage.cc 171 : : fts/fts0fts.cc 183 : : fts/fts0plugin.cc 184 : : handler/ha_innodb.cc 185 : : handler/handler0alter.cc 186 : : + handler/innodb_binlog.cc 187 : : handler/i_s.cc 188 : : ibuf/ibuf0ibuf.cc 189 : : include/btr0btr.h 239 : : include/fsp0space.h 240 : : include/fsp0sysspace.h 241 : : include/fsp0types.h 242 : : + include/fsp_binlog.h 243 : : include/fts0ast.h 244 : : include/fts0blex.h 245 : : include/fts0fts.h 262 : : include/ha0storage.h 263 : : include/ha0storage.inl 264 : : include/handler0alter.h 265 : : + include/innodb_binlog.h 266 : : include/hash0hash.h 267 : : include/ibuf0ibuf.h 268 : : include/lock0iter.h ===== File: storage/innobase/buf/buf0flu.cc ===== 41 : : #include "log0crypt.h" 42 : : #include "srv0mon.h" 43 : : #include "fil0pagecompress.h" 44 : : +#include "fsp_binlog.h" 45 : : #include "lzo/lzo1x.h" 46 : : #include "snappy-c.h" 47 : : 2036 : 32261 : return true; 2037 : : } 2038 : : 2039 : 179894 : +void mtr_t::write_binlog(page_id_t page_id, uint16_t offset, 2040 : : + const void *buf, size_t size) noexcept 2041 : : +{ 2042 : 179894 : + ut_ad(!srv_read_only_mode); 2043 : 179894 : + ut_ad(m_log_mode == MTR_LOG_ALL); 2044 : : + 2045 : 179894 : + bool alloc{size < mtr_buf_t::MAX_DATA_SIZE - (1 + 3 + 3 + 5 + 5)}; 2046 : 179894 : + byte *end= log_write(page_id, nullptr, size, alloc, offset); 2047 : 179894 : + if (alloc) 2048 : : + { 2049 : 137345 : + ::memcpy(end, buf, size); 2050 : 137345 : + m_log.close(end + size); 2051 : : + } 2052 : : + else 2053 : : + { 2054 : 42549 : + m_log.close(end); 2055 : 42549 : + m_log.push(static_cast(buf), uint32_t(size)); 2056 : : + } 2057 : 179894 : + m_modifications= true; 2058 : 179894 : +} 2059 : : + 2060 : : /** Make a checkpoint. Note that this function does not flush dirty 2061 : : blocks from the buffer pool: it only checks what is lsn of the oldest 2062 : : modification in the pool, and writes information about the lsn in 2077 : : #endif 2078 : : 2079 : 82220 : fil_flush_file_spaces(); 2080 : 82220 : + binlog_write_up_to_now(); 2081 : : 2082 : 82220 : log_sys.latch.wr_lock(SRW_LOCK_CALL); 2083 : 82220 : const lsn_t end_lsn= log_sys.get_lsn(); 2084 : 82220 : mysql_mutex_lock(&buf_pool.flush_list_mutex); 2085 : 82220 : const lsn_t oldest_lsn= buf_pool.get_oldest_modification(end_lsn); 2086 : : + // FIXME: limit oldest_lsn below binlog split write LSN 2087 : 82220 : mysql_mutex_unlock(&buf_pool.flush_list_mutex); 2088 : 82220 : return log_checkpoint_low(oldest_lsn, end_lsn); 2089 : : } 2279 : : 2280 : 7233 : os_aio_wait_until_no_pending_writes(false); 2281 : 7233 : fil_flush_file_spaces(); 2282 : 7233 : + binlog_write_up_to_now(); 2283 : : 2284 : 7233 : log_sys.latch.wr_lock(SRW_LOCK_CALL); 2285 : 7233 : const lsn_t newest_lsn= log_sys.get_lsn(); 2286 : 7233 : mysql_mutex_lock(&buf_pool.flush_list_mutex); 2287 : 7233 : lsn_t measure= buf_pool.get_oldest_modification(0); 2288 : 7233 : const lsn_t checkpoint_lsn= measure ? measure : newest_lsn; 2289 : : + // FIXME: limit checkpoint_lsn below binlog split write LSN 2290 : : 2291 : 14353 : if (!recv_recovery_is_on() && 2292 : 7120 : checkpoint_lsn > log_sys.last_checkpoint_lsn + SIZE_OF_FILE_CHECKPOINT) ===== File: storage/innobase/fil/fil0fil.cc ===== 186 : : const char* fil_path_to_mysql_datadir; 187 : : 188 : : /** Common InnoDB file extensions */ 189 : : +const char* dot_ext[] = { "", ".ibd", ".isl", ".cfg", ".ibb" }; 190 : : 191 : : /** Number of pending tablespace flushes */ 192 : : Atomic_counter fil_n_pending_tablespace_flushes; 1563 : 630298 : ut_ad(!is_predefined_tablespace(space_id)); 1564 : : 1565 : : /* fil_name_parse() requires that there be at least one path 1566 : : + separator and that the file path end with ".ibd" or "ibb". */ 1567 : 630299 : ut_ad(strchr(path, '/')); 1568 : 630299 : + ut_ad(!strcmp(&path[strlen(path) - strlen(DOT_IBD)], DOT_IBD) || 1569 : : + !strcmp(&path[strlen(path) - strlen(DOT_IBB)], DOT_IBB)); 1570 : : 1571 : 630299 : m_modifications= true; 1572 : 630299 : if (!is_logged()) ===== File: storage/innobase/fsp/fsp0fsp.cc ===== 1055 : : @param[in] offset page number of the allocated page 1056 : : @param[in,out] mtr mini-transaction 1057 : : @return block, initialized */ 1058 : 4378166 : +buf_block_t* fsp_page_create(fil_space_t *space, uint32_t offset, 1059 : : + mtr_t *mtr) noexcept 1060 : : { 1061 : 4378166 : buf_block_t *free_block= buf_LRU_get_free_block(have_no_mutex), 1062 : 4378213 : *block= buf_page_create(space, offset, space->zip_size(), mtr, free_block); ===== File: storage/innobase/fsp/fsp_binlog.cc ===== 1 : : +/***************************************************************************** 2 : : + 3 : : +Copyright (c) 2024, Kristian Nielsen 4 : : + 5 : : +This program is free software; you can redistribute it and/or modify it under 6 : : +the terms of the GNU General Public License as published by the Free Software 7 : : +Foundation; version 2 of the License. 8 : : + 9 : : +This program is distributed in the hope that it will be useful, but WITHOUT 10 : : +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 : : +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 : : + 13 : : +You should have received a copy of the GNU General Public License along with 14 : : +this program; if not, write to the Free Software Foundation, Inc., 15 : : +51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA 16 : : + 17 : : +*****************************************************************************/ 18 : : + 19 : : +/**************************************************//** 20 : : +@file fsp/fsp_binlog.cc 21 : : +InnoDB implementation of binlog. 22 : : +*******************************************************/ 23 : : + 24 : : +#include 25 : : +#include "fsp0fsp.h" 26 : : +#include "buf0flu.h" 27 : : +#include "trx0trx.h" 28 : : +#include "fsp_binlog.h" 29 : : +#include "innodb_binlog.h" 30 : : + 31 : : +#include "my_bit.h" 32 : : +#include "rpl_gtid_base.h" 33 : : +#include "log.h" 34 : : + 35 : : + 36 : : +/** 37 : : + The page size used for binlog pages. 38 : : + 39 : : + For now, we just use a 16k page size. It could be changed later to be 40 : : + configurable, changing the page size of the binlog is much easier than for 41 : : + normal InnoDB tablespaces, as we could simply flush out the current file and 42 : : + create the next file with a different page size, just need to put the page 43 : : + size somewhere in the file header. 44 : : + 45 : : + On the other hand, the page size does not seem to be very significant for 46 : : + performance or anything. All data can be split across to the next page, and 47 : : + everything is written in sequence through the kernel buffer cache which is 48 : : + then free to flush it to disk in whatever chunk sizes it wants. 49 : : +*/ 50 : : +uint32_t ibb_page_size_shift= 14; 51 : : +ulong ibb_page_size= (1 << ibb_page_size_shift); 52 : : + 53 : : + 54 : : +/** 55 : : + How often (in terms of pages written) to dump a (differential) binlog state 56 : : + at the start of the page, to speed up finding the initial GTID position for 57 : : + a connecting slave. 58 : : + 59 : : + This value must be used over the setting innodb_binlog_state_interval, 60 : : + because after a restart the latest binlog file will be using the value of the 61 : : + setting prior to the restart; the new value of the setting (if different) 62 : : + will be used for newly created binlog files. The value refers to the file 63 : : + of active_binlog_file_no. 64 : : +*/ 65 : : +uint64_t current_binlog_state_interval; 66 : : + 67 : : +/** 68 : : + Mutex protecting active_binlog_file_no. 69 : : +*/ 70 : : +mysql_mutex_t active_binlog_mutex; 71 : : +pthread_cond_t active_binlog_cond; 72 : : +/** Mutex protecting binlog_cur_durable_offset[] and ibb_pending_lsn_fifo. */ 73 : : +mysql_mutex_t binlog_durable_mutex; 74 : : +mysql_cond_t binlog_durable_cond; 75 : : + 76 : : +/** The currently being written binlog tablespace. */ 77 : : +std::atomic active_binlog_file_no; 78 : : + 79 : : +/** 80 : : + The first binlog tablespace that is still open. 81 : : + This can be equal to active_binlog_file_no, if the tablespace prior to the 82 : : + active one has been fully flushed out to disk and closed. 83 : : + Or it can be one less, if the prior tablespace is still being written out and 84 : : + closed. 85 : : +*/ 86 : : +uint64_t first_open_binlog_file_no; 87 : : + 88 : : +/** 89 : : + The most recent created and open tablespace. 90 : : + This can be equal to active_binlog_file_no+1, if the next tablespace to be 91 : : + used has already been pre-allocated and opened. 92 : : + Or it can be the same as active_binlog_file_no, if the pre-allocation of the 93 : : + next tablespace is still pending. 94 : : +*/ 95 : : +uint64_t last_created_binlog_file_no; 96 : : + 97 : : +/** 98 : : + Point at which it is guaranteed that all data has been written out to the 99 : : + binlog file (on the OS level; not necessarily fsync()'ed yet). 100 : : + 101 : : + Stores the most recent four values, each corresponding to 102 : : + active_binlog_file_no&4. This is so that it can be always valid for both 103 : : + active and active-1 (active-2 is always durable, as we make the entire binlog 104 : : + file N durable before pre-allocating N+2). Just before active moves to the 105 : : + next file_no, we can set the value for active+1, leaving active and active-1 106 : : + still valid. (Only 3 entries are needed, but we use four to be able to use 107 : : + bit-wise and instead of modulo-3). 108 : : +*/ 109 : : +std::atomic binlog_cur_durable_offset[4]; 110 : : +/** 111 : : + Offset of last valid byte of data in most recent 4 binlog files. 112 : : + A value of ~0 means that file is not opened as a tablespace (and data is 113 : : + valid until the end of the file). 114 : : +*/ 115 : : +std::atomic binlog_cur_end_offset[4]; 116 : : + 117 : : +fsp_binlog_page_fifo *binlog_page_fifo; 118 : : + 119 : : +/** Object to keep track of outstanding oob references in binlog files. */ 120 : : +ibb_file_oob_refs ibb_file_hash; 121 : : + 122 : : + 123 : : +fsp_binlog_page_entry * 124 : 21017 : +fsp_binlog_page_fifo::get_entry(uint64_t file_no, uint64_t page_no, 125 : : + uint32_t latch, bool completed, bool clean) 126 : : +{ 127 : 21017 : + mysql_mutex_assert_owner(&m_mutex); 128 : 21017 : + ut_a(file_no == first_file_no || file_no == first_file_no + 1); 129 : 21017 : + page_list *pl= &fifos[file_no & 1]; 130 : 21017 : + ut_ad(pl->first_page_no + pl->used_entries == page_no); 131 : 21017 : + if (UNIV_UNLIKELY(pl->used_entries == pl->allocated_entries)) 132 : : + { 133 : 0 : + size_t new_allocated_entries= 2*pl->allocated_entries; 134 : 0 : + size_t new_size= new_allocated_entries * sizeof(*pl->entries); 135 : : + fsp_binlog_page_entry **new_entries= 136 : 0 : + (fsp_binlog_page_entry **)ut_realloc(pl->entries, new_size); 137 : 0 : + if (!new_entries) 138 : 0 : + return nullptr; 139 : : + /* Copy any wrapped-around elements into not-wrapped new locations. */ 140 : 0 : + if (pl->first_entry + pl->used_entries > pl->allocated_entries) 141 : : + { 142 : 0 : + size_t wrapped_entries= 143 : 0 : + pl->first_entry + pl->used_entries - pl->allocated_entries; 144 : 0 : + ut_ad(new_allocated_entries >= pl->allocated_entries + wrapped_entries); 145 : 0 : + memcpy(new_entries + pl->allocated_entries, new_entries, 146 : : + wrapped_entries * sizeof(*new_entries)); 147 : : + } 148 : 0 : + pl->entries= new_entries; 149 : 0 : + pl->allocated_entries= new_allocated_entries; 150 : : + } 151 : 21017 : + fsp_binlog_page_entry *&e_loc= pl->entry_at(pl->used_entries); 152 : 21017 : + ++pl->used_entries; 153 : 21017 : + if (UNIV_LIKELY(freelist != nullptr)) 154 : : + { 155 : 16669 : + e_loc= (fsp_binlog_page_entry *)freelist; 156 : 16669 : + freelist= (byte *)*(uintptr_t *)freelist; 157 : 16669 : + --free_buffers; 158 : : + } 159 : : + else 160 : : + { 161 : : + byte *mem= 162 : 8696 : + static_cast(ut_malloc(sizeof(*e_loc) + ibb_page_size, 163 : : + mem_key_binlog)); 164 : 4348 : + if (!mem) 165 : 0 : + return nullptr; 166 : 4348 : + e_loc= (fsp_binlog_page_entry *)mem; 167 : : + } 168 : 21017 : + e_loc->latched= latch; 169 : 21017 : + e_loc->last_page= (page_no + 1 == size_in_pages(file_no)); 170 : 21017 : + e_loc->complete= completed; 171 : 21017 : + e_loc->flushed_clean= clean; 172 : 21017 : + e_loc->pending_flush= false; 173 : 21017 : + return e_loc; 174 : : +} 175 : : + 176 : : + 177 : : +void 178 : 21009 : +fsp_binlog_page_fifo::release_entry(uint64_t file_no, uint64_t page_no) 179 : : +{ 180 : 21009 : + ut_a(file_no == first_file_no || file_no == first_file_no + 1); 181 : 21009 : + page_list *pl= &fifos[file_no & 1]; 182 : 21009 : + ut_a(page_no == pl->first_page_no); 183 : 21009 : + fsp_binlog_page_entry *e= pl->entries[pl->first_entry]; 184 : 21009 : + ut_ad(pl->used_entries > 0); 185 : 21009 : + --pl->used_entries; 186 : 21009 : + ++pl->first_entry; 187 : 21009 : + ++pl->first_page_no; 188 : 21009 : + if (UNIV_UNLIKELY(pl->first_entry == pl->allocated_entries)) 189 : 8 : + pl->first_entry= 0; 190 : : + /* 191 : : + Put the page buffer on the freelist. Unless we already have too much on the 192 : : + freelist; then put it on a temporary list so it can be freed later, outside 193 : : + of holding the mutex. 194 : : + */ 195 : 21009 : + if (UNIV_LIKELY(free_buffers * MAX_FREE_BUFFERS_FRAC <= 196 : : + innodb_binlog_size_in_pages)) 197 : : + { 198 : 20675 : + *(uintptr_t *)e= (uintptr_t)freelist; 199 : 20675 : + freelist= (byte *)e; 200 : 20675 : + ++free_buffers; 201 : : + } 202 : : + else 203 : : + { 204 : 334 : + *(uintptr_t *)e= (uintptr_t)to_free_list; 205 : 334 : + to_free_list= (byte *)e; 206 : : + } 207 : 21009 : +} 208 : : + 209 : : + 210 : : +void 211 : 114510 : +fsp_binlog_page_fifo::unlock_with_delayed_free() 212 : : +{ 213 : 114510 : + mysql_mutex_assert_owner(&m_mutex); 214 : 114510 : + byte *to_free= to_free_list; 215 : 114510 : + to_free_list= nullptr; 216 : 114510 : + mysql_mutex_unlock(&m_mutex); 217 : 114510 : + if (UNIV_UNLIKELY(to_free != nullptr)) 218 : : + { 219 : : + do 220 : : + { 221 : 334 : + byte *next= (byte *)*(uintptr_t *)to_free; 222 : 334 : + ut_free(to_free); 223 : 334 : + to_free= next; 224 : 334 : + } while (to_free); 225 : : + } 226 : 114510 : +} 227 : : + 228 : : + 229 : : +fsp_binlog_page_entry * 230 : 20972 : +fsp_binlog_page_fifo::create_page(uint64_t file_no, uint32_t page_no) 231 : : +{ 232 : 20972 : + mysql_mutex_lock(&m_mutex); 233 : 20972 : + ut_ad(first_file_no != ~(uint64_t)0); 234 : 20972 : + ut_a(file_no == first_file_no || file_no == first_file_no + 1); 235 : : + /* Can only allocate pages consecutively. */ 236 : 20972 : + ut_a(page_no == fifos[file_no & 1].first_page_no + 237 : : + fifos[file_no & 1].used_entries); 238 : 20972 : + fsp_binlog_page_entry *e= get_entry(file_no, page_no, 1, false, false); 239 : 20972 : + ut_a(e); 240 : 20972 : + mysql_mutex_unlock(&m_mutex); 241 : 20972 : + memset(e->page_buf(), 0, ibb_page_size); 242 : : + 243 : 20972 : + return e; 244 : : +} 245 : : + 246 : : + 247 : : +fsp_binlog_page_entry * 248 : 183457 : +fsp_binlog_page_fifo::get_page(uint64_t file_no, uint32_t page_no) 249 : : +{ 250 : 183457 : + fsp_binlog_page_entry *res= nullptr; 251 : : + page_list *pl; 252 : : + 253 : 183457 : + mysql_mutex_lock(&m_mutex); 254 : : + 255 : 183464 : + ut_ad(first_file_no != ~(uint64_t)0); 256 : 183464 : + ut_a(file_no <= first_file_no + 1); 257 : 183464 : + if (file_no < first_file_no) 258 : 6203 : + goto end; 259 : 177261 : + pl= &fifos[file_no & 1]; 260 : 177261 : + if (page_no >= pl->first_page_no && 261 : 167782 : + page_no < pl->first_page_no + pl->used_entries) 262 : : + { 263 : 167782 : + res= pl->entry_at(page_no - pl->first_page_no); 264 : 167782 : + ++res->latched; 265 : : + } 266 : : + 267 : 9479 : +end: 268 : 183464 : + mysql_mutex_unlock(&m_mutex); 269 : 183464 : + return res; 270 : : +} 271 : : + 272 : : + 273 : : +void 274 : 188736 : +fsp_binlog_page_fifo::release_page(fsp_binlog_page_entry *page) 275 : : +{ 276 : 188736 : + mysql_mutex_lock(&m_mutex); 277 : 188754 : + ut_a(page->latched > 0); 278 : 188754 : + if (--page->latched == 0 && (page->complete || page->pending_flush)) 279 : 20325 : + pthread_cond_broadcast(&m_cond); /* Page ready to be flushed to disk */ 280 : 188754 : + mysql_mutex_unlock(&m_mutex); 281 : 188754 : +} 282 : : + 283 : : + 284 : : +/** 285 : : + Release a page that is part of an mtr, except that if this is the last page 286 : : + of a binlog tablespace, then delay release until mtr commit. 287 : : + 288 : : + This is used to make sure that a tablespace is not closed until any mtr that 289 : : + modified it has been committed and the modification redo logged. This way, a 290 : : + closed tablespace never needs recovery and at most the two most recent binlog 291 : : + tablespaces need to be considered during recovery. 292 : : +*/ 293 : : +void 294 : 177813 : +fsp_binlog_page_fifo::release_page_mtr(fsp_binlog_page_entry *page, mtr_t *mtr) 295 : : +{ 296 : 177813 : + if (!page->last_page) 297 : 168297 : + return release_page(page); 298 : : + 299 : : + /* 300 : : + Check against having two pending last-in-binlog-file pages to release. 301 : : + But allow to have the same page released twice in a single mtr (this can 302 : : + happen when 2-phase commit puts an XID/XA complete record just in front 303 : : + of the commit record). 304 : : + */ 305 : 9516 : + fsp_binlog_page_entry *old_page= mtr->get_binlog_page(); 306 : 9516 : + ut_ad(!(old_page != nullptr && old_page != page)); 307 : 9516 : + if (UNIV_UNLIKELY(old_page != nullptr)) 308 : : + { 309 : 2 : + if (UNIV_UNLIKELY(old_page != page)) 310 : 0 : + sql_print_error("InnoDB: Internal inconsistency with mini-transaction " 311 : : + "that spans more than two binlog files. Recovery may " 312 : : + "be affected until the next checkpoint."); 313 : 2 : + release_page(old_page); 314 : : + } 315 : 9516 : + mtr->set_binlog_page(page); 316 : : +} 317 : : + 318 : : + 319 : : +/* Check if there are any (complete) non-flushed pages in a tablespace. */ 320 : : +bool 321 : 62808 : +fsp_binlog_page_fifo::has_unflushed(uint64_t file_no) 322 : : +{ 323 : 62808 : + mysql_mutex_assert_owner(&m_mutex); 324 : 62808 : + if (UNIV_UNLIKELY(file_no < first_file_no)) 325 : 0 : + return false; 326 : 62808 : + if (UNIV_UNLIKELY(file_no > first_file_no + 1)) 327 : 0 : + return false; 328 : 62808 : + const page_list *pl= &fifos[file_no & 1]; 329 : 62808 : + if (pl->used_entries == 0) 330 : 25801 : + return false; 331 : 37007 : + if (!pl->entries[pl->first_entry]->complete) 332 : 18088 : + return false; 333 : 18919 : + ut_ad(!pl->entries[pl->first_entry]->flushed_clean 334 : : + /* Clean and complete page should have been freed */); 335 : 18919 : + return true; 336 : : +} 337 : : + 338 : : + 339 : : +/** 340 : : + Flush (write to disk) the first unflushed page in a file. 341 : : + Returns true when the last page has been flushed. 342 : : + 343 : : + Must be called with m_mutex held. 344 : : + 345 : : + If called with force=true, will flush even any final, incomplete page. 346 : : + Otherwise such page will not be written out. Any final, incomplete page 347 : : + is left in the FIFO in any case. 348 : : +*/ 349 : : +void 350 : 23602 : +fsp_binlog_page_fifo::flush_one_page(uint64_t file_no, bool force) 351 : : +{ 352 : : + page_list *pl; 353 : : + fsp_binlog_page_entry *e; 354 : : + 355 : 23602 : + mysql_mutex_assert_owner(&m_mutex); 356 : : + /* 357 : : + Wait for the FIFO to be not flushing from another thread, and for the 358 : : + first page to not be latched. 359 : : + */ 360 : : + for (;;) 361 : : + { 362 : : + /* 363 : : + Let's make page not present not an error, to allow races where someone else 364 : : + flushed the page ahead of us. 365 : : + */ 366 : 29554 : + if (file_no < first_file_no) 367 : 408 : + return; 368 : : + /* Guard against simultaneous RESET MASTER. */ 369 : 29146 : + if (file_no > first_file_no + 1) 370 : 0 : + return; 371 : : + 372 : 29146 : + if (!flushing) 373 : : + { 374 : 25739 : + pl= &fifos[file_no & 1]; 375 : 25739 : + if (pl->used_entries == 0) 376 : 738 : + return; 377 : 25001 : + e= pl->entries[pl->first_entry]; 378 : 25001 : + if (e->latched == 0) 379 : 22456 : + break; 380 : 2545 : + if (force) 381 : 765 : + e->pending_flush= true; 382 : : + } 383 : 5952 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 384 : : + } 385 : 22456 : + flushing= true; 386 : 22456 : + uint32_t page_no= pl->first_page_no; 387 : 22456 : + bool is_complete= e->complete; 388 : 22456 : + ut_ad(is_complete || pl->used_entries == 1); 389 : 22456 : + if (is_complete || (force && !e->flushed_clean)) 390 : : + { 391 : : + /* 392 : : + Careful here! We are going to release the mutex while flushing the page 393 : : + to disk. At this point, another thread might come in and add more data 394 : : + to the page in parallel, if e->complete is not set! 395 : : + 396 : : + So here we set flushed_clean _before_ releasing the mutex. Then any 397 : : + other thread that in parallel latches the page and tries to update it in 398 : : + parallel will either increment e->latched, or set e->flushed_clean back 399 : : + to false (or both). This allows us to detect a parallel update and retry 400 : : + the write in that case. 401 : : + */ 402 : 22413 : + retry: 403 : 22420 : + if (!is_complete) 404 : 3281 : + e->flushed_clean= true; 405 : 22420 : + e->pending_flush= false; 406 : : + /* Release the mutex, then free excess page buffers while not holding it. */ 407 : 22420 : + unlock_with_delayed_free(); 408 : 22420 : + File fh= get_fh(file_no); 409 : 22420 : + ut_a(pl->fh >= (File)0); 410 : 22420 : + size_t res= crc32_pwrite_page(fh, e->page_buf(), page_no, MYF(MY_WME)); 411 : 22420 : + ut_a(res == ibb_page_size); 412 : 22420 : + mysql_mutex_lock(&m_mutex); 413 : 22420 : + if (UNIV_UNLIKELY(e->latched) || 414 : 22349 : + (!is_complete && UNIV_UNLIKELY(!e->flushed_clean))) 415 : : + { 416 : 75 : + flushing= false; 417 : 75 : + pthread_cond_broadcast(&m_cond); 418 : : + for (;;) 419 : : + { 420 : 158 : + ut_ad(file_no < first_file_no || 421 : : + pl->first_page_no >= page_no); 422 : 158 : + ut_ad(file_no < first_file_no || 423 : : + pl->first_page_no > page_no || 424 : : + pl->entries[pl->first_entry] == e); 425 : 158 : + if (!flushing) 426 : : + { 427 : 148 : + if (file_no < first_file_no || 428 : 147 : + pl->first_page_no != page_no || 429 : 143 : + pl->entries[pl->first_entry] != e) 430 : : + { 431 : : + /* Someone else flushed the page for us. */ 432 : 5 : + return; 433 : : + } 434 : : + /* Guard against simultaneous RESET MASTER. */ 435 : 143 : + if (file_no > first_file_no + 1) 436 : 0 : + return; 437 : 143 : + if (e->latched == 0) 438 : 70 : + break; 439 : 73 : + if (force) 440 : 14 : + e->pending_flush= true; 441 : : + } 442 : 83 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 443 : : + } 444 : 70 : + flushing= true; 445 : 70 : + if (!is_complete) 446 : : + { 447 : : + /* 448 : : + The page was not complete, a writer may have added data. Need to redo 449 : : + the flush. 450 : : + */ 451 : 7 : + is_complete= e->complete; 452 : 7 : + goto retry; 453 : : + } 454 : : + /* 455 : : + The page was complete, but was latched while we were flushing (by a 456 : : + reader). No need to flush again, just needed to wait until the latch 457 : : + was released before we can continue to free the page. 458 : : + */ 459 : : + } 460 : : + } 461 : : + /* 462 : : + We marked the FIFO as flushing, page could not have disappeared despite 463 : : + releasing the mutex during the I/O. 464 : : + */ 465 : 22451 : + ut_ad(flushing); 466 : 22451 : + ut_ad(pl->used_entries >= 1); 467 : 22451 : + if (is_complete) 468 : 19136 : + release_entry(file_no, page_no); 469 : 22451 : + flushing= false; 470 : 22451 : + pthread_cond_broadcast(&m_cond); 471 : : +} 472 : : + 473 : : + 474 : : +void 475 : 90366 : +fsp_binlog_page_fifo::flush_up_to(uint64_t file_no, uint32_t page_no) 476 : : +{ 477 : 90366 : + mysql_mutex_lock(&m_mutex); 478 : : + for (;;) 479 : : + { 480 : 95059 : + const page_list *pl= &fifos[file_no & 1]; 481 : 95059 : + if (file_no < first_file_no || 482 : 13542 : + (file_no == first_file_no && pl->first_page_no > page_no)) 483 : : + break; 484 : : + /* Guard against simultaneous RESET MASTER. */ 485 : 11241 : + if (file_no > first_file_no + 1) 486 : 0 : + break; 487 : : + /* 488 : : + The flush is complete if there are no pages left, or if there is just 489 : : + one incomplete page left that is fully flushed so far. 490 : : + */ 491 : 11241 : + if (pl->used_entries == 0 || 492 : 7965 : + (pl->used_entries == 1 && !pl->entries[pl->first_entry]->complete && 493 : 5742 : + pl->entries[pl->first_entry]->flushed_clean)) 494 : : + break; 495 : 4683 : + uint64_t file_no_to_flush= file_no; 496 : : + /* Flush the prior file to completion first. */ 497 : 4683 : + if (file_no == first_file_no + 1 && fifos[(file_no - 1) & 1].used_entries) 498 : : + { 499 : 1 : + file_no_to_flush= file_no - 1; 500 : 1 : + pl= &fifos[file_no_to_flush & 1]; 501 : 1 : + ut_ad(pl->entries[pl->first_entry]->complete); 502 : : + } 503 : 4683 : + flush_one_page(file_no_to_flush, true); 504 : 4683 : + } 505 : : + /* Will release the mutex and free any excess page buffers. */ 506 : 90376 : + unlock_with_delayed_free(); 507 : 90376 : +} 508 : : + 509 : : + 510 : : +void 511 : 84777 : +fsp_binlog_page_fifo::do_fdatasync(uint64_t file_no) 512 : : +{ 513 : : + File fh; 514 : 84777 : + mysql_mutex_lock(&m_mutex); 515 : : + for (;;) 516 : : + { 517 : 84787 : + if (file_no < first_file_no) 518 : 81517 : + break; /* Old files are already fully synced. */ 519 : : + /* Guard against simultaneous RESET MASTER. */ 520 : 3270 : + if (file_no > first_file_no + 1) 521 : 0 : + break; 522 : 3270 : + fh= fifos[file_no & 1].fh; 523 : 3270 : + if (fh <= (File)-1) 524 : 0 : + break; 525 : 3270 : + if (flushing) 526 : : + { 527 : 20 : + while (flushing) 528 : 10 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 529 : 10 : + continue; /* Loop again to recheck state, as we released the mutex */ 530 : : + } 531 : 3260 : + flushing= true; 532 : 3260 : + mysql_mutex_unlock(&m_mutex); 533 : 3260 : + int res= my_sync(fh, MYF(MY_WME)); 534 : 3260 : + ut_a(!res); 535 : 3260 : + mysql_mutex_lock(&m_mutex); 536 : 3260 : + flushing= false; 537 : 3260 : + pthread_cond_broadcast(&m_cond); 538 : 3260 : + break; 539 : 10 : + } 540 : 84777 : + mysql_mutex_unlock(&m_mutex); 541 : 84777 : +} 542 : : + 543 : : + 544 : : +File 545 : 22850 : +fsp_binlog_page_fifo::get_fh(uint64_t file_no) 546 : : +{ 547 : 22850 : + File fh= fifos[file_no & 1].fh; 548 : 22850 : + if (fh == (File)-1) 549 : : + { 550 : : + char filename[OS_FILE_MAX_PATH]; 551 : 3276 : + binlog_name_make(filename, file_no); 552 : 3276 : + fifos[file_no & 1].fh= fh= my_open(filename, O_RDWR | O_BINARY, MYF(MY_WME)); 553 : : + } 554 : 22850 : + return fh; 555 : : +} 556 : : + 557 : : +/** 558 : : + If init_page is not ~(uint32_t)0, then it is the page to continue writing 559 : : + when re-opening existing binlog at server startup. 560 : : + 561 : : + If in addition, partial_page is non-NULL, it is an (aligned) page buffer 562 : : + containing the partial data of page init_page. 563 : : + 564 : : + If init_page is set but partial_page is not, then init_page is the first, 565 : : + empty page in the tablespace to create and start writing to. 566 : : +*/ 567 : : +void 568 : 5161 : +fsp_binlog_page_fifo::create_tablespace(uint64_t file_no, 569 : : + uint32_t size_in_pages, 570 : : + uint32_t init_page, 571 : : + byte *partial_page) 572 : : +{ 573 : 5161 : + mysql_mutex_lock(&m_mutex); 574 : 5161 : + ut_ad(init_page == ~(uint32_t)0 || 575 : : + first_file_no == ~(uint64_t)0 || 576 : : + /* At server startup allow opening N empty and (N-1) partial. */ 577 : : + (init_page != ~(uint32_t)0 && file_no + 1 == first_file_no && 578 : : + fifos[first_file_no & 1].used_entries == 0)); 579 : 5161 : + ut_a(first_file_no == ~(uint64_t)0 || 580 : : + file_no == first_file_no + 1 || 581 : : + file_no == first_file_no + 2 || 582 : : + (init_page != ~(uint32_t)0 && file_no + 1 == first_file_no && 583 : : + fifos[first_file_no & 1].used_entries == 0)); 584 : 5161 : + page_list *pl= &fifos[file_no & 1]; 585 : 5161 : + if (first_file_no == ~(uint64_t)0) 586 : : + { 587 : 1883 : + first_file_no= file_no; 588 : : + } 589 : 3278 : + else if (UNIV_UNLIKELY(file_no + 1 == first_file_no)) 590 : 28 : + first_file_no= file_no; 591 : 3250 : + else if (file_no == first_file_no + 2) 592 : : + { 593 : : + /* All pages in (N-2) must be flushed before doing (N). */ 594 : 0 : + ut_a(pl->used_entries == 0); 595 : 0 : + if (UNIV_UNLIKELY(pl->fh != (File)-1)) 596 : : + { 597 : 0 : + ut_ad(false /* Should have been done as part of tablespace close. */); 598 : 0 : + my_close(pl->fh, MYF(0)); 599 : : + } 600 : 0 : + first_file_no= file_no - 1; 601 : : + } 602 : : + 603 : 5161 : + pl->fh= (File)-1; 604 : 5161 : + pl->size_in_pages= size_in_pages; 605 : 5161 : + ut_ad(pl->used_entries == 0); 606 : 5161 : + ut_ad(pl->first_entry == 0); 607 : 5161 : + if (UNIV_UNLIKELY(init_page != ~(uint32_t)0)) 608 : : + { 609 : 47 : + pl->first_page_no= init_page; 610 : 47 : + if (partial_page) 611 : : + { 612 : 45 : + fsp_binlog_page_entry *e= get_entry(file_no, init_page, 0, false, true); 613 : 45 : + ut_a(e); 614 : 45 : + memcpy(e->page_buf(), partial_page, ibb_page_size); 615 : : + } 616 : : + } 617 : : + else 618 : 5114 : + pl->first_page_no= 0; 619 : 5161 : + pthread_cond_broadcast(&m_cond); 620 : 5161 : + mysql_mutex_unlock(&m_mutex); 621 : 5161 : +} 622 : : + 623 : : + 624 : : +void 625 : 5145 : +fsp_binlog_page_fifo::release_tablespace(uint64_t file_no) 626 : : +{ 627 : 5145 : + mysql_mutex_lock(&m_mutex); 628 : 5145 : + page_list *pl= &fifos[file_no & 1]; 629 : 5145 : + ut_a(file_no == first_file_no); 630 : 5145 : + ut_a(pl->used_entries == 0 || 631 : : + /* Allow a final, incomplete-but-fully-flushed page in the fifo. */ 632 : : + (!pl->entries[pl->first_entry]->complete && 633 : : + pl->entries[pl->first_entry]->flushed_clean && 634 : : + pl->used_entries == 1 && 635 : : + fifos[(file_no + 1) & 1].used_entries == 0)); 636 : 5145 : + if (pl->fh != (File)-1) 637 : : + { 638 : 3795 : + while (flushing) 639 : 527 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 640 : 3268 : + flushing= true; 641 : 3268 : + File fh= pl->fh; 642 : 3268 : + mysql_mutex_unlock(&m_mutex); 643 : 3268 : + int res= my_sync(fh, MYF(MY_WME)); 644 : 3268 : + ut_a(!res); 645 : 3268 : + mysql_mutex_lock(&m_mutex); 646 : 3268 : + free_page_list(file_no); 647 : 3268 : + flushing= false; 648 : 3268 : + pthread_cond_broadcast(&m_cond); 649 : : + } 650 : 5145 : + first_file_no= file_no + 1; 651 : 5145 : + mysql_mutex_unlock(&m_mutex); 652 : 5145 : +} 653 : : + 654 : : + 655 : 11023 : +fsp_binlog_page_fifo::fsp_binlog_page_fifo() 656 : 11023 : + : first_file_no(~(uint64_t)0), free_buffers(0), freelist(nullptr), 657 : 11023 : + to_free_list(nullptr), flushing(false), 658 : 11023 : + flush_thread_started(false), flush_thread_end(false) 659 : : +{ 660 : 33069 : + for (unsigned i= 0; i < 2; ++i) 661 : : + { 662 : 22046 : + fifos[i].allocated_entries= 64; 663 : 22046 : + fifos[i].entries= 664 : 44092 : + (fsp_binlog_page_entry **)ut_malloc(fifos[i].allocated_entries * 665 : : + sizeof(fsp_binlog_page_entry *), 666 : : + mem_key_binlog); 667 : 22046 : + ut_a(fifos[i].entries); 668 : 22046 : + fifos[i].used_entries= 0; 669 : 22046 : + fifos[i].first_entry= 0; 670 : 22046 : + fifos[i].first_page_no= 0; 671 : 22046 : + fifos[i].size_in_pages= 0; 672 : 22046 : + fifos[i].fh= (File)-1; 673 : : + } 674 : 11023 : + mysql_mutex_init(fsp_page_fifo_mutex_key, &m_mutex, nullptr); 675 : 11023 : + pthread_cond_init(&m_cond, nullptr); 676 : 11023 : +} 677 : : + 678 : : + 679 : : +void 680 : 7018 : +fsp_binlog_page_fifo::free_page_list(uint64_t file_no) 681 : : +{ 682 : 7018 : + page_list *pl= &fifos[file_no & 1]; 683 : 7018 : + if (pl->fh != (File)-1) 684 : 3268 : + my_close(pl->fh, MYF(0)); 685 : 8891 : + while (pl->used_entries > 0) 686 : : + { 687 : 1873 : + memset(pl->entries[pl->first_entry]->page_buf(), 0, ibb_page_size); 688 : 1873 : + release_entry(file_no, pl->first_page_no); 689 : : + } 690 : : + /* We hold on to the pl->entries array and reuse for next tablespace. */ 691 : 7018 : + pl->used_entries= 0; 692 : 7018 : + pl->first_entry= 0; 693 : 7018 : + pl->first_page_no= 0; 694 : 7018 : + pl->size_in_pages= 0; 695 : 7018 : + pl->fh= (File)-1; 696 : 7018 : +} 697 : : + 698 : : + 699 : : +void 700 : 11139 : +fsp_binlog_page_fifo::reset() 701 : : +{ 702 : 11139 : + ut_ad(!flushing); 703 : 11139 : + if (first_file_no != ~(uint64_t)0) 704 : : + { 705 : 5625 : + for (uint32_t i= 0; i < 2; ++i) 706 : 3750 : + free_page_list(first_file_no + i); 707 : : + } 708 : 11139 : + first_file_no= ~(uint64_t)0; 709 : : + /* Release page buffers in the freelist. */ 710 : 15131 : + while (freelist) 711 : : + { 712 : 3992 : + byte *q= (byte *)*(uintptr_t *)freelist; 713 : 3992 : + ut_free(freelist); 714 : 3992 : + freelist= q; 715 : : + } 716 : 11139 : + free_buffers= 0; 717 : 11139 : + while (to_free_list) 718 : : + { 719 : 0 : + byte *q= (byte *)*(uintptr_t *)to_free_list; 720 : 0 : + ut_free(to_free_list); 721 : 0 : + to_free_list= q; 722 : : + } 723 : 11139 : +} 724 : : + 725 : : + 726 : 9425 : +fsp_binlog_page_fifo::~fsp_binlog_page_fifo() 727 : : +{ 728 : 9425 : + ut_ad(!flushing); 729 : 9425 : + reset(); 730 : 28275 : + for (uint32_t i= 0; i < 2; ++i) 731 : 18850 : + ut_free(fifos[i].entries); 732 : 9425 : + mysql_mutex_destroy(&m_mutex); 733 : 9425 : + pthread_cond_destroy(&m_cond); 734 : 9425 : +} 735 : : + 736 : : + 737 : : +void 738 : 2144 : +fsp_binlog_page_fifo::lock_wait_for_idle() 739 : : +{ 740 : 2144 : + mysql_mutex_lock(&m_mutex); 741 : 2144 : + while(flushing) 742 : 0 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 743 : 2144 : +} 744 : : + 745 : : + 746 : : +void 747 : 11023 : +fsp_binlog_page_fifo::start_flush_thread() 748 : : +{ 749 : 11023 : + flush_thread_started= false; 750 : 11023 : + flush_thread_end= false; 751 : 22046 : + flush_thread_obj= std::thread{ [this] { flush_thread_run(); } }; 752 : 11023 : + mysql_mutex_lock(&m_mutex); 753 : 22037 : + while (!flush_thread_started) 754 : 11014 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 755 : 11023 : + mysql_mutex_unlock(&m_mutex); 756 : 11023 : +} 757 : : + 758 : : + 759 : : +void 760 : 9425 : +fsp_binlog_page_fifo::stop_flush_thread() 761 : : +{ 762 : 9425 : + if (!flush_thread_started) 763 : 0 : + return; 764 : 9425 : + mysql_mutex_lock(&m_mutex); 765 : 9425 : + flush_thread_end= true; 766 : 9425 : + pthread_cond_broadcast(&m_cond); 767 : 18850 : + while (flush_thread_started) 768 : 9425 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 769 : 9425 : + mysql_mutex_unlock(&m_mutex); 770 : 9425 : + flush_thread_obj.join(); 771 : : +} 772 : : + 773 : : + 774 : : +void 775 : 11023 : +fsp_binlog_page_fifo::flush_thread_run() 776 : : +{ 777 : 11023 : + mysql_mutex_lock(&m_mutex); 778 : 11023 : + flush_thread_started= true; 779 : 11023 : + pthread_cond_broadcast(&m_cond); 780 : : + 781 : 61056 : + while (!flush_thread_end) 782 : : + { 783 : : + /* 784 : : + Flush pages one by one as long as there are more pages pending. 785 : : + Once all have been flushed, wait for more pages to become pending. 786 : : + Don't try to force flush a final page that is not yet completely 787 : : + filled with data. 788 : : + */ 789 : 51631 : + uint64_t file_no= first_file_no; 790 : 51631 : + if (first_file_no != ~(uint64_t)0) 791 : : + { 792 : 40088 : + if (has_unflushed(file_no)) 793 : : + { 794 : 17368 : + flush_one_page(file_no, false); 795 : 17368 : + continue; // Check again for more pages available to flush 796 : : + } 797 : 22720 : + else if (has_unflushed(file_no + 1)) 798 : : + { 799 : 1551 : + flush_one_page(file_no + 1, false); 800 : 1551 : + continue; 801 : : + } 802 : : + } 803 : 32712 : + if (!flush_thread_end) 804 : 32712 : + my_cond_wait(&m_cond, &m_mutex.m_mutex); 805 : : + } 806 : : + 807 : 9425 : + flush_thread_started= false; 808 : 9425 : + pthread_cond_broadcast(&m_cond); 809 : 9425 : + mysql_mutex_unlock(&m_mutex); 810 : 9425 : +} 811 : : + 812 : : + 813 : : +size_t 814 : 22632 : +crc32_pwrite_page(File fd, byte *buf, uint32_t page_no, myf MyFlags) noexcept 815 : : +{ 816 : 22632 : + const uint32_t payload= (uint32_t)ibb_page_size - BINLOG_PAGE_CHECKSUM; 817 : 22632 : + int4store(buf + payload, my_crc32c(0, buf, payload)); 818 : 45264 : + return my_pwrite(fd, (const uchar *)buf, ibb_page_size, 819 : 22632 : + (my_off_t)page_no << ibb_page_size_shift, MyFlags); 820 : : +} 821 : : + 822 : : + 823 : : +/** 824 : : + Read a page, with CRC check. 825 : : + Returns: 826 : : + 827 : : + -1 error 828 : : + 0 EOF 829 : : + 1 Ok 830 : : +*/ 831 : : +int 832 : 43820 : +crc32_pread_page(File fd, byte *buf, uint32_t page_no, myf MyFlags) noexcept 833 : : +{ 834 : 87640 : + size_t read= my_pread(fd, buf, ibb_page_size, 835 : 43820 : + (my_off_t)page_no << ibb_page_size_shift, MyFlags); 836 : 43820 : + int res= 1; 837 : 43820 : + if (UNIV_LIKELY(read == ibb_page_size)) 838 : : + { 839 : 43814 : + const uint32_t payload= (uint32_t)ibb_page_size - BINLOG_PAGE_CHECKSUM; 840 : 43814 : + uint32_t crc32= uint4korr(buf + payload); 841 : : + /* Allow a completely zero (empty) page as well. */ 842 : 43816 : + if (UNIV_UNLIKELY(crc32 != my_crc32c(0, buf, payload)) && 843 : 2 : + (buf[0] != 0 || 0 != memcmp(buf, buf+1, ibb_page_size - 1))) 844 : : + { 845 : 2 : + res= -1; 846 : 2 : + my_errno= EIO; 847 : 2 : + if (MyFlags & MY_WME) 848 : : + { 849 : 2 : + sql_print_error("InnoDB: Page corruption in binlog tablespace file " 850 : : + "page number %u (invalid crc32 checksum 0x%08X)", 851 : : + page_no, crc32); 852 : 2 : + my_error(ER_BINLOG_READ_EVENT_CHECKSUM_FAILURE, MYF(0)); 853 : : + } 854 : : + } 855 : : + } 856 : 6 : + else if (read == (size_t)-1) 857 : : + { 858 : 0 : + if (MyFlags & MY_WME) 859 : 0 : + my_error(ER_READING_BINLOG_FILE, MYF(0), page_no, my_errno); 860 : 0 : + res= -1; 861 : : + } 862 : : + else 863 : 6 : + res= 0; 864 : : + 865 : 43820 : + return res; 866 : : +} 867 : : + 868 : : + 869 : : +int 870 : 301 : +crc32_pread_page(pfs_os_file_t fh, byte *buf, uint32_t page_no, myf MyFlags) 871 : : + noexcept 872 : : +{ 873 : 301 : + const uint32_t page_size= (uint32_t)ibb_page_size; 874 : 301 : + ulint bytes_read= 0; 875 : 301 : + dberr_t err= os_file_read(IORequestRead, fh, buf, 876 : : + (os_offset_t)page_no << ibb_page_size_shift, 877 : : + page_size, &bytes_read); 878 : 301 : + if (UNIV_UNLIKELY(err != DB_SUCCESS)) 879 : 0 : + return -1; 880 : 301 : + else if (UNIV_UNLIKELY(bytes_read < page_size)) 881 : 0 : + return 0; 882 : : + 883 : 301 : + const uint32_t payload= (uint32_t)ibb_page_size - BINLOG_PAGE_CHECKSUM; 884 : 301 : + uint32_t crc32= uint4korr(buf + payload); 885 : : + /* Allow a completely zero (empty) page as well. */ 886 : 429 : + if (UNIV_UNLIKELY(crc32 != my_crc32c(0, buf, payload)) && 887 : 128 : + (buf[0] != 0 || 0 != memcmp(buf, buf+1, ibb_page_size - 1))) 888 : : + { 889 : 0 : + my_errno= EIO; 890 : 0 : + if (MyFlags & MY_WME) 891 : 0 : + sql_print_error("InnoDB: Page corruption in binlog tablespace file " 892 : : + "page number %u (invalid crc32 checksum 0x%08X)", 893 : : + page_no, crc32); 894 : 0 : + return -1; 895 : : + } 896 : 301 : + return 1; 897 : : +} 898 : : + 899 : : + 900 : : +/** 901 : : + Need specific constructor/initializer for struct ibb_tblspc_entry stored in 902 : : + the ibb_file_hash. This is a work-around for C++ abstractions that makes it 903 : : + non-standard behaviour to memcpy() std::atomic objects. 904 : : +*/ 905 : : +static void 906 : 1449 : +ibb_file_hash_constructor(uchar *arg) 907 : : +{ 908 : 1449 : + new(arg + LF_HASH_OVERHEAD) ibb_tblspc_entry(); 909 : 1449 : +} 910 : : + 911 : : + 912 : : +static void 913 : 1413 : +ibb_file_hash_destructor(uchar *arg) 914 : : +{ 915 : 1413 : + ibb_tblspc_entry *e= 916 : 1413 : + reinterpret_cast(arg + LF_HASH_OVERHEAD); 917 : 1413 : + e->~ibb_tblspc_entry(); 918 : 1413 : +} 919 : : + 920 : : + 921 : : +static void 922 : 5171 : +ibb_file_hash_initializer(LF_HASH *hash, void *dst, const void *src) 923 : : +{ 924 : 5171 : + const ibb_tblspc_entry *src_e= static_cast(src); 925 : 5171 : + ibb_tblspc_entry *dst_e= 926 : : + const_cast(static_cast(dst)); 927 : 5171 : + dst_e->file_no= src_e->file_no; 928 : 10342 : + dst_e->oob_refs.store(src_e->oob_refs.load(std::memory_order_relaxed), 929 : : + std::memory_order_relaxed); 930 : 10342 : + dst_e->xa_refs.store(src_e->xa_refs.load(std::memory_order_relaxed), 931 : : + std::memory_order_relaxed); 932 : 10342 : + dst_e->oob_ref_file_no.store(src_e->oob_ref_file_no.load(std::memory_order_relaxed), 933 : : + std::memory_order_relaxed); 934 : 10342 : + dst_e->xa_ref_file_no.store(src_e->xa_ref_file_no.load(std::memory_order_relaxed), 935 : : + std::memory_order_relaxed); 936 : 5171 : +} 937 : : + 938 : : + 939 : : +void 940 : 11023 : +ibb_file_oob_refs::init() noexcept 941 : : +{ 942 : 11023 : + lf_hash_init(&hash, sizeof(ibb_tblspc_entry), LF_HASH_UNIQUE, 943 : : + offsetof(ibb_tblspc_entry, file_no), 944 : : + sizeof(ibb_tblspc_entry::file_no), nullptr, nullptr); 945 : 11023 : + hash.alloc.constructor= ibb_file_hash_constructor; 946 : 11023 : + hash.alloc.destructor= ibb_file_hash_destructor; 947 : 11023 : + hash.initializer= ibb_file_hash_initializer; 948 : 11023 : + earliest_oob_ref= ~(uint64_t)0; 949 : 11023 : + earliest_xa_ref= ~(uint64_t)0; 950 : 11023 : +} 951 : : + 952 : : + 953 : : +void 954 : 9425 : +ibb_file_oob_refs::destroy() noexcept 955 : : +{ 956 : 9425 : + lf_hash_destroy(&hash); 957 : 9425 : +} 958 : : + 959 : : + 960 : : +void 961 : 4104 : +ibb_file_oob_refs::remove(uint64_t file_no, LF_PINS *pins) 962 : : +{ 963 : 4104 : + lf_hash_delete(&hash, pins, &file_no, sizeof(file_no)); 964 : 4104 : +} 965 : : + 966 : : + 967 : : +void 968 : 1714 : +ibb_file_oob_refs::remove_up_to(uint64_t file_no, LF_PINS *pins) 969 : : +{ 970 : : + for (;;) 971 : : + { 972 : 3950 : + int res= lf_hash_delete(&hash, pins, &file_no, sizeof(file_no)); 973 : 3950 : + if (res || file_no == 0) 974 : : + break; 975 : 2236 : + --file_no; 976 : 2236 : + } 977 : 1714 : +} 978 : : + 979 : : + 980 : : +uint64_t 981 : 1409 : +ibb_file_oob_refs::oob_ref_inc(uint64_t file_no, LF_PINS *pins, bool do_xa) 982 : : +{ 983 : : + ibb_tblspc_entry *e= static_cast 984 : 1409 : + (lf_hash_search(&hash, pins, &file_no, sizeof(file_no))); 985 : 1409 : + if (!e) 986 : 0 : + return ~(uint64_t)0; 987 : 1409 : + uint64_t refcnt= e->oob_refs.fetch_add(1, std::memory_order_acquire); 988 : 1409 : + if (UNIV_UNLIKELY(do_xa)) 989 : 240 : + refcnt= e->xa_refs.fetch_add(1, std::memory_order_acquire); 990 : 1409 : + lf_hash_search_unpin(pins); 991 : 1409 : + return refcnt + 1; 992 : : +} 993 : : + 994 : : + 995 : : +uint64_t 996 : 1385 : +ibb_file_oob_refs::oob_ref_dec(uint64_t file_no, LF_PINS *pins, bool do_xa) 997 : : +{ 998 : : + ibb_tblspc_entry *e= static_cast 999 : 1385 : + (lf_hash_search(&hash, pins, &file_no, sizeof(file_no))); 1000 : 1385 : + if (!e) 1001 : 0 : + return ~(uint64_t)0; 1002 : 1385 : + uint64_t oob_refcnt= e->oob_refs.fetch_sub(1, std::memory_order_acquire) - 1; 1003 : 1385 : + uint64_t ret_refcnt= oob_refcnt; 1004 : 1385 : + if (UNIV_UNLIKELY(do_xa)) 1005 : : + { 1006 : 96 : + mysql_mutex_assert_owner(&ibb_xa_xid_hash->xid_mutex); 1007 : 192 : + ret_refcnt= e->xa_refs.fetch_sub(1, std::memory_order_acquire) - 1; 1008 : : + } 1009 : 1385 : + lf_hash_search_unpin(pins); 1010 : 1385 : + ut_ad(oob_refcnt != (uint64_t)0 - 1); 1011 : : + 1012 : 1385 : + if (oob_refcnt == 0) 1013 : 1165 : + do_zero_refcnt_action(file_no, pins, false); 1014 : 1385 : + return ret_refcnt; 1015 : : +} 1016 : : + 1017 : : + 1018 : : +void 1019 : 2560 : +ibb_file_oob_refs::do_zero_refcnt_action(uint64_t file_no, LF_PINS *pins, 1020 : : + bool active_moving) 1021 : : +{ 1022 : : + for (;;) 1023 : : + { 1024 : : + ibb_tblspc_entry *e= static_cast 1025 : 4453 : + (lf_hash_search(&hash, pins, &file_no, sizeof(file_no))); 1026 : 4453 : + if (!e) 1027 : 6 : + return; 1028 : 4447 : + uint64_t refcnt= e->oob_refs.load(std::memory_order_acquire); 1029 : 4447 : + lf_hash_search_unpin(pins); 1030 : 4447 : + if (refcnt > 0) 1031 : 297 : + return; 1032 : : + /* 1033 : : + Reference count reached zero. Check if this was the earliest_oob_ref 1034 : : + that reached zero, and if so move it to the next file. Repeat this 1035 : : + for consecutive refcount-is-zero file_no, in case N+1 reaches zero 1036 : : + before N does. 1037 : : + 1038 : : + As records are written into the active binlog file, the refcount can 1039 : : + reach zero temporarily and then go up again, so do not move the 1040 : : + earliest_oob_ref ahead yet. 1041 : : + 1042 : : + As the active is about to move to the next file, we check again, and 1043 : : + this time move the earliest_oob_ref if the refcount on the (previously) 1044 : : + active binlog file ended up at zero. 1045 : : + */ 1046 : 4150 : + uint64_t active= active_binlog_file_no.load(std::memory_order_acquire); 1047 : 4150 : + ut_ad(file_no <= active + active_moving); 1048 : 4150 : + if (file_no >= active + active_moving) 1049 : 2257 : + return; 1050 : : + bool ok; 1051 : : + do 1052 : : + { 1053 : 1893 : + uint64_t read_file_no= earliest_oob_ref.load(std::memory_order_relaxed); 1054 : 1893 : + if (read_file_no != file_no) 1055 : 510 : + break; 1056 : 1383 : + ok= earliest_oob_ref.compare_exchange_weak(read_file_no, file_no + 1, 1057 : : + std::memory_order_relaxed); 1058 : 1383 : + } while (!ok); 1059 : : + /* Handle any following file_no that may have dropped to zero earlier. */ 1060 : 1893 : + ++file_no; 1061 : 1893 : + } 1062 : : +} 1063 : : + 1064 : : + 1065 : : +bool 1066 : 3231 : +ibb_file_oob_refs::update_refs(uint64_t file_no, LF_PINS *pins, 1067 : : + uint64_t oob_ref, uint64_t xa_ref) 1068 : : +{ 1069 : : + ibb_tblspc_entry *e= static_cast 1070 : 3231 : + (lf_hash_search(&hash, pins, &file_no, sizeof(file_no))); 1071 : 3231 : + if (!e) 1072 : 0 : + return false; 1073 : 3231 : + e->oob_ref_file_no.store(oob_ref, std::memory_order_relaxed); 1074 : 3231 : + e->xa_ref_file_no.store(xa_ref, std::memory_order_relaxed); 1075 : 3231 : + lf_hash_search_unpin(pins); 1076 : 3231 : + return true; 1077 : : +} 1078 : : + 1079 : : + 1080 : : +/* 1081 : : + This is called when an xa/xid refcount goes from 1->0 or 0->1, to update 1082 : : + the value of ibb_file_hash.earliest_xa_ref if necessary. 1083 : : +*/ 1084 : : +void 1085 : 198 : +ibb_file_oob_refs::update_earliest_xa_ref(uint64_t ref_file_no, LF_PINS *pins) 1086 : : +{ 1087 : 198 : + mysql_mutex_assert_owner(&ibb_xa_xid_hash->xid_mutex); 1088 : 198 : + uint64_t file_no1= earliest_xa_ref.load(std::memory_order_relaxed); 1089 : 198 : + if (file_no1 < ref_file_no) 1090 : : + { 1091 : : + /* Current is before the updated one, no change possible for now. */ 1092 : 10 : + return; 1093 : : + } 1094 : 188 : + uint64_t file_no2= active_binlog_file_no.load(std::memory_order_acquire); 1095 : 188 : + uint64_t file_no= ref_file_no; 1096 : : + for (;;) 1097 : : + { 1098 : 320 : + if (file_no > file_no2) 1099 : : + { 1100 : : + /* No active XA anymore. */ 1101 : 81 : + file_no= ~(uint64_t)0; 1102 : 81 : + break; 1103 : : + } 1104 : : + ibb_tblspc_entry *e= static_cast 1105 : 239 : + (lf_hash_search(&hash, pins, &file_no, sizeof(file_no))); 1106 : 239 : + if (!e) 1107 : : + { 1108 : 10 : + ++file_no; 1109 : 10 : + continue; 1110 : : + } 1111 : 229 : + uint64_t refcnt= e->xa_refs.load(std::memory_order_acquire); 1112 : 229 : + lf_hash_search_unpin(pins); 1113 : 229 : + if (refcnt > 0) 1114 : 107 : + break; 1115 : 122 : + ++file_no; 1116 : 132 : + } 1117 : 188 : + earliest_xa_ref.store(file_no, std::memory_order_relaxed); 1118 : : +} 1119 : : + 1120 : : + 1121 : : +/** 1122 : : + Look up the earliest file with OOB references from a given file_no. 1123 : : + Insert a new entry into the file hash (reading the file header from disk) 1124 : : + if not there already. 1125 : : +*/ 1126 : : +bool 1127 : 112 : +ibb_file_oob_refs::get_oob_ref_file_no(uint64_t file_no, LF_PINS *pins, 1128 : : + uint64_t *out_oob_ref_file_no) 1129 : : +{ 1130 : : + ibb_tblspc_entry *e= static_cast 1131 : 112 : + (lf_hash_search(&hash, pins, &file_no, sizeof(file_no))); 1132 : 112 : + if (e) 1133 : : + { 1134 : 102 : + *out_oob_ref_file_no= e->oob_ref_file_no.load(std::memory_order_relaxed); 1135 : 102 : + lf_hash_search_unpin(pins); 1136 : 102 : + return false; 1137 : : + } 1138 : : + 1139 : 10 : + *out_oob_ref_file_no= ~(uint64_t)0; 1140 : 20 : + byte *page_buf= static_cast(ut_malloc(ibb_page_size, mem_key_binlog)); 1141 : 10 : + if (!page_buf) 1142 : : + { 1143 : 0 : + my_error(ER_OUTOFMEMORY, MYF(0), ibb_page_size); 1144 : 0 : + return true; 1145 : : + } 1146 : : + char filename[OS_FILE_MAX_PATH]; 1147 : 10 : + binlog_name_make(filename, file_no); 1148 : 10 : + File fh= my_open(filename, O_RDONLY | O_BINARY, MYF(0)); 1149 : 10 : + if (fh < (File)0) 1150 : : + { 1151 : 0 : + my_error(ER_ERROR_ON_READ, MYF(0), filename, my_errno); 1152 : 0 : + ut_free(page_buf); 1153 : 0 : + return true; 1154 : : + } 1155 : 10 : + int res= crc32_pread_page(fh, page_buf, 0, MYF(0)); 1156 : 10 : + my_close(fh, MYF(0)); 1157 : 10 : + if (res <= 0) 1158 : : + { 1159 : 0 : + ut_free(page_buf); 1160 : 0 : + my_error(ER_ERROR_ON_READ, MYF(0), filename, my_errno); 1161 : 0 : + return true; 1162 : : + } 1163 : : + binlog_header_data header; 1164 : 10 : + fsp_binlog_extract_header_page(page_buf, &header); 1165 : 10 : + ut_free(page_buf); 1166 : 10 : + if (header.is_invalid || header.is_empty) 1167 : : + { 1168 : 0 : + my_error(ER_FILE_CORRUPT, MYF(0), filename); 1169 : 0 : + return true; 1170 : : + } 1171 : 10 : + *out_oob_ref_file_no= header.oob_ref_file_no; 1172 : 10 : + if (ibb_record_in_file_hash(file_no, header.oob_ref_file_no, 1173 : : + header.xa_ref_file_no, pins)) 1174 : 0 : + return true; 1175 : : + 1176 : 10 : + return false; 1177 : : +} 1178 : : + 1179 : : + 1180 : : +/* 1181 : : + Check if a file_no contains oob data that is needed by an active 1182 : : + (ie. not committed) transaction. This is seen simply as having refcount 1183 : : + greater than 0. 1184 : : +*/ 1185 : : +bool 1186 : 2404 : +ibb_file_oob_refs::get_oob_ref_in_use(uint64_t file_no, LF_PINS *pins) 1187 : : +{ 1188 : : + ibb_tblspc_entry *e= static_cast 1189 : 2404 : + (lf_hash_search(&hash, pins, &file_no, sizeof(file_no))); 1190 : 2404 : + if (!e) 1191 : 44 : + return false; 1192 : : + 1193 : 2360 : + uint64_t refcnt= e->oob_refs.load(std::memory_order_relaxed); 1194 : 2360 : + lf_hash_search_unpin(pins); 1195 : 2360 : + return refcnt > 0; 1196 : : +} 1197 : : + 1198 : : + 1199 : : +/* 1200 : : + Check if there are any of the in-use binlog files that have refcount > 0 1201 : : + (meaning any references to oob data from active transactions). 1202 : : + Any such references must prevent a RESET MASTER, as otherwise they could 1203 : : + be committed with OOB references pointing to garbage data. 1204 : : +*/ 1205 : : +bool 1206 : 1718 : +ibb_file_oob_refs::check_any_oob_ref_in_use(uint64_t start_file_no, 1207 : : + uint64_t end_file_no, 1208 : : + LF_PINS *lf_pins) 1209 : : +{ 1210 : 1718 : + if (unlikely(start_file_no == ~(uint64_t)0) 1211 : 1718 : + || unlikely(end_file_no == ~(uint64_t)0)) 1212 : 0 : + return false; 1213 : : + 1214 : 3962 : + for (uint64_t file_no= start_file_no; file_no <= end_file_no; ++file_no) 1215 : : + { 1216 : 2248 : + if (get_oob_ref_in_use(file_no, lf_pins)) 1217 : 4 : + return true; 1218 : : + } 1219 : 1714 : + return false; 1220 : : +} 1221 : : + 1222 : : + 1223 : : +bool 1224 : 5171 : +ibb_record_in_file_hash(uint64_t file_no, uint64_t oob_ref, uint64_t xa_ref, 1225 : : + LF_PINS *in_pins) 1226 : : +{ 1227 : 5171 : + bool err= false; 1228 : 5171 : + LF_PINS *pins= in_pins ? in_pins : lf_hash_get_pins(&ibb_file_hash.hash); 1229 : 5171 : + if (!pins) 1230 : : + { 1231 : 0 : + my_error(ER_OUT_OF_RESOURCES, MYF(0)); 1232 : 0 : + return true; 1233 : : + } 1234 : : + ibb_tblspc_entry entry; 1235 : 5171 : + entry.file_no= file_no; 1236 : : + entry.oob_refs.store(0, std::memory_order_relaxed); 1237 : : + entry.xa_refs.store(0, std::memory_order_relaxed); 1238 : : + entry.oob_ref_file_no.store(oob_ref, std::memory_order_relaxed); 1239 : : + entry.xa_ref_file_no.store(xa_ref, std::memory_order_relaxed); 1240 : 5171 : + int res= lf_hash_insert(&ibb_file_hash.hash, pins, &entry); 1241 : 5171 : + if (res) 1242 : : + { 1243 : 0 : + ut_ad(res < 0 /* Should not get unique violation, never insert twice */); 1244 : 0 : + sql_print_error("InnoDB: Could not initialize in-memory structure for " 1245 : : + "binlog tablespace file number %" PRIu64 ", %s", file_no, 1246 : : + (res < 0 ? "out of memory" : "internal error")); 1247 : 0 : + err= true; 1248 : : + } 1249 : 5171 : + if (!in_pins) 1250 : 75 : + lf_hash_put_pins(pins); 1251 : 5171 : + return err; 1252 : : +} 1253 : : + 1254 : : + 1255 : : +void 1256 : 89411 : +binlog_write_up_to_now() noexcept 1257 : : +{ 1258 : 89411 : + fsp_binlog_page_fifo *fifo= binlog_page_fifo; 1259 : 89411 : + if (!fifo) 1260 : 6508 : + return; /* Startup eg. */ 1261 : : + 1262 : 82940 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 1263 : : + uint64_t active2; 1264 : : + uint32_t page_no; 1265 : : + do 1266 : : + { 1267 : 82940 : + active2= active; 1268 : 82940 : + page_no= binlog_cur_page_no; 1269 : 82940 : + active= active_binlog_file_no.load(std::memory_order_relaxed); 1270 : 82940 : + } while (UNIV_UNLIKELY(active != active2)); 1271 : : + 1272 : 82940 : + if (active != ~(uint64_t)0) 1273 : : + { 1274 : 82939 : + fifo->flush_up_to(active, page_no); 1275 : 82941 : + fifo->do_fdatasync(active); 1276 : : + } 1277 : : +} 1278 : : + 1279 : : + 1280 : : +void 1281 : 2908 : +fsp_binlog_extract_header_page(const byte *page_buf, 1282 : : + binlog_header_data *out_header_data) noexcept 1283 : : +{ 1284 : 2908 : + uint32_t magic= uint4korr(page_buf); 1285 : 2908 : + uint32_t vers_major= uint4korr(page_buf + 8); 1286 : 2908 : + const uint32_t payload= IBB_HEADER_PAGE_SIZE - BINLOG_PAGE_CHECKSUM; 1287 : 2908 : + uint32_t crc32= uint4korr(page_buf + payload); 1288 : 2908 : + out_header_data->is_empty= false; 1289 : 2908 : + out_header_data->is_invalid= false; 1290 : 5788 : + if (crc32 != my_crc32c(0, page_buf, payload) || 1291 : 5788 : + magic != IBB_MAGIC || vers_major > IBB_FILE_VERS_MAJOR) 1292 : : + { 1293 : 28 : + if (page_buf[0] == 0 && 1294 : 28 : + 0 == memcmp(page_buf, page_buf+1, IBB_HEADER_PAGE_SIZE - 1)) 1295 : 28 : + out_header_data->is_empty= true; 1296 : : + else 1297 : 0 : + out_header_data->is_invalid= true; 1298 : 28 : + return; 1299 : : + } 1300 : 2880 : + out_header_data->page_size_shift= uint4korr(page_buf + 4); 1301 : 2880 : + out_header_data->vers_major= vers_major; 1302 : 2880 : + out_header_data->vers_minor= uint4korr(page_buf + 12); 1303 : 2880 : + out_header_data->file_no= uint8korr(page_buf + 16); 1304 : 2880 : + out_header_data-> page_count= uint8korr(page_buf + 24); 1305 : 2880 : + out_header_data-> start_lsn= uint8korr(page_buf + 32); 1306 : 2880 : + out_header_data-> diff_state_interval= uint8korr(page_buf + 40); 1307 : 2880 : + out_header_data->oob_ref_file_no= uint8korr(page_buf + 48); 1308 : 2880 : + out_header_data->xa_ref_file_no= uint8korr(page_buf + 56); 1309 : : +} 1310 : : + 1311 : : + 1312 : : +void 1313 : 176663 : +fsp_log_binlog_write(mtr_t *mtr, fsp_binlog_page_entry *page, 1314 : : + uint64_t file_no, uint32_t page_no, 1315 : : + uint32_t page_offset, uint32_t len) 1316 : : +{ 1317 : 176663 : + ut_ad(page->latched); 1318 : 176663 : + if (page_offset + len >= ibb_page_size - BINLOG_PAGE_DATA_END) 1319 : 15905 : + page->complete= true; 1320 : 176663 : + if (page->flushed_clean) 1321 : : + { 1322 : : + /* 1323 : : + If the page with partial data has been written to the file system, then 1324 : : + redo log all the data on the page, to be sure we can still recover the 1325 : : + entire page reliably even if the latest checkpoint is after that partial 1326 : : + write. 1327 : : + */ 1328 : 1453 : + len= page_offset + len; 1329 : 1453 : + page_offset= 0; 1330 : 1453 : + page->flushed_clean= false; 1331 : : + } 1332 : 176663 : + page_id_t page_id(LOG_BINLOG_ID_0 | static_cast(file_no & 1), 1333 : 176663 : + page_no); 1334 : 353326 : + mtr->write_binlog(page_id, (uint16_t)page_offset, 1335 : 176663 : + page_offset + &page->page_buf()[0], len); 1336 : 176663 : +} 1337 : : + 1338 : : + 1339 : : +void 1340 : 3231 : +fsp_log_header_page(mtr_t *mtr, fsp_binlog_page_entry *page, uint64_t file_no, 1341 : : + uint32_t len) 1342 : : + noexcept 1343 : : +{ 1344 : 3231 : + page->complete= true; 1345 : 3231 : + page_id_t page_id(LOG_BINLOG_ID_0 | static_cast(file_no & 1), 0); 1346 : 3231 : + mtr->write_binlog(page_id, 0, &page->page_buf()[0], len); 1347 : 3231 : +} 1348 : : + 1349 : : + 1350 : : +/** 1351 : : + Initialize the InnoDB implementation of binlog. 1352 : : + Note that we do not create or open any binlog tablespaces here. 1353 : : + This is only done if InnoDB binlog is enabled on the server level. 1354 : : +*/ 1355 : : +dberr_t 1356 : 11023 : +fsp_binlog_init() 1357 : : +{ 1358 : 11023 : + mysql_mutex_init(fsp_active_binlog_mutex_key, &active_binlog_mutex, nullptr); 1359 : 11023 : + pthread_cond_init(&active_binlog_cond, nullptr); 1360 : 11023 : + mysql_mutex_init(fsp_binlog_durable_mutex_key, &binlog_durable_mutex, nullptr); 1361 : 11023 : + mysql_cond_init(fsp_binlog_durable_cond_key, &binlog_durable_cond, nullptr); 1362 : 11023 : + mysql_mutex_record_order(&binlog_durable_mutex, &active_binlog_mutex); 1363 : : + 1364 : 11023 : + ibb_file_hash.init(); 1365 : 11023 : + binlog_page_fifo= new fsp_binlog_page_fifo(); 1366 : 11023 : + if (UNIV_UNLIKELY(!binlog_page_fifo)) 1367 : : + { 1368 : 0 : + sql_print_error("InnoDB: Could not allocate memory for the page fifo, " 1369 : : + "cannot proceed"); 1370 : 0 : + return DB_OUT_OF_MEMORY; 1371 : : + } 1372 : 11023 : + binlog_page_fifo->start_flush_thread(); 1373 : 11023 : + return DB_SUCCESS; 1374 : : +} 1375 : : + 1376 : : + 1377 : : +void 1378 : 9425 : +fsp_binlog_shutdown() 1379 : : +{ 1380 : 9425 : + binlog_page_fifo->stop_flush_thread(); 1381 : 9425 : + delete binlog_page_fifo; 1382 : 9425 : + ibb_file_hash.destroy(); 1383 : 9425 : + mysql_cond_destroy(&binlog_durable_cond); 1384 : 9425 : + mysql_mutex_destroy(&binlog_durable_mutex); 1385 : 9425 : + pthread_cond_destroy(&active_binlog_cond); 1386 : 9425 : + mysql_mutex_destroy(&active_binlog_mutex); 1387 : 9425 : +} 1388 : : + 1389 : : + 1390 : : +/** Write out all pages, flush, and close/detach a binlog tablespace. 1391 : : +@param[in] file_no Index of the binlog tablespace 1392 : : +@return DB_SUCCESS or error code */ 1393 : : +dberr_t 1394 : 5145 : +fsp_binlog_tablespace_close(uint64_t file_no) 1395 : : +{ 1396 : 5145 : + binlog_page_fifo->flush_up_to(file_no, ~(uint32_t)0); 1397 : : + uint32_t size= 1398 : 5145 : + binlog_page_fifo->size_in_pages(file_no) << ibb_page_size_shift; 1399 : : + /* release_tablespace() will fdatasync() the file first. */ 1400 : 5145 : + binlog_page_fifo->release_tablespace(file_no); 1401 : : + /* 1402 : : + Durably sync the redo log. This simplifies things a bit, as then we know 1403 : : + that we will not need to discard any data from an old binlog file during 1404 : : + recovery, at most from the latest two existing files. 1405 : : + */ 1406 : 5145 : + log_buffer_flush_to_disk(true); 1407 : : + uint64_t end_offset= 1408 : 5145 : + binlog_cur_end_offset[file_no & 3].load(std::memory_order_relaxed); 1409 : 5145 : + binlog_cur_end_offset[file_no & 3].store(size, std::memory_order_relaxed); 1410 : : + /* 1411 : : + Wait for the last record in the file to be marked durably synced to the 1412 : : + (redo) log. We already ensured that the record is durable with the above 1413 : : + call to log_buffer_flush_to_disk(); this way, we ensure that the update 1414 : : + of binlog_cur_durable_offset[] happens correctly through the 1415 : : + ibb_pending_lsn_fifo, so that the current durable position will be 1416 : : + consistent with a recorded LSN, and a reader will not see EOF in the 1417 : : + middle of a record. 1418 : : + */ 1419 : : + uint64_t dur_offset= 1420 : 5145 : + binlog_cur_durable_offset[file_no & 3].load(std:: memory_order_relaxed); 1421 : 5145 : + if (dur_offset < end_offset) 1422 : 590 : + ibb_wait_durable_offset(file_no, end_offset); 1423 : : + 1424 : 5145 : + return DB_SUCCESS; 1425 : : +} 1426 : : + 1427 : : + 1428 : : +/** 1429 : : + Open an existing tablespace. The filehandle fh is taken over by the tablespace 1430 : : + (or closed in case of error). 1431 : : +*/ 1432 : : +bool 1433 : 75 : +fsp_binlog_open(const char *file_name, pfs_os_file_t fh, 1434 : : + uint64_t file_no, size_t file_size, 1435 : : + uint32_t init_page, byte *partial_page) 1436 : : +{ 1437 : 75 : + const uint32_t page_size= (uint32_t)ibb_page_size; 1438 : 75 : + const uint32_t page_size_shift= ibb_page_size_shift; 1439 : : + 1440 : 75 : + os_offset_t binlog_size= innodb_binlog_size_in_pages << ibb_page_size_shift; 1441 : 75 : + if (init_page == ~(uint32_t)0 && file_size < binlog_size) { 1442 : : + /* 1443 : : + A crash may have left a partially pre-allocated file. If so, extend it 1444 : : + to the required size. 1445 : : + Note that this may also extend a previously pre-allocated file to the new 1446 : : + binlog configured size, if the configuration changed during server 1447 : : + restart. 1448 : : + */ 1449 : 0 : + if (!os_file_set_size(file_name, fh, binlog_size, false)) { 1450 : 0 : + sql_print_warning("Failed to change the size of InnoDB binlog file '%s' " 1451 : : + "from %zu to %zu bytes (error code: %d)", file_name, 1452 : 0 : + file_size, (size_t)binlog_size, errno); 1453 : : + } else { 1454 : 0 : + file_size= (size_t)binlog_size; 1455 : : + } 1456 : : + } 1457 : 75 : + if (file_size < 2*page_size) 1458 : : + { 1459 : 0 : + sql_print_warning("InnoDB binlog file number %" PRIu64 " is too short " 1460 : : + "(%zu bytes), should be at least %u bytes", 1461 : : + file_no, file_size, 2*page_size); 1462 : 0 : + os_file_close(fh); 1463 : 0 : + return true; 1464 : : + } 1465 : : + 1466 : 75 : + binlog_page_fifo->create_tablespace(file_no, 1467 : 75 : + (uint32_t)(file_size >> page_size_shift), 1468 : : + init_page, partial_page); 1469 : 75 : + os_file_close(fh); 1470 : 75 : + first_open_binlog_file_no= file_no; 1471 : 75 : + if (last_created_binlog_file_no == ~(uint64_t)0 || 1472 : 28 : + file_no > last_created_binlog_file_no) { 1473 : 47 : + last_created_binlog_file_no= file_no; 1474 : : + } 1475 : 75 : + return false; 1476 : : +} 1477 : : + 1478 : : + 1479 : : +/** Create a binlog tablespace file 1480 : : +@param[in] file_no Index of the binlog tablespace 1481 : : +@return DB_SUCCESS or error code */ 1482 : 5086 : +dberr_t fsp_binlog_tablespace_create(uint64_t file_no, uint32_t size_in_pages, 1483 : : + LF_PINS *pins) 1484 : : +{ 1485 : 5086 : + pfs_os_file_t fh; 1486 : : + bool ret; 1487 : : + 1488 : 5086 : + if(srv_read_only_mode) 1489 : 0 : + return DB_ERROR; 1490 : : + 1491 : : + char name[OS_FILE_MAX_PATH]; 1492 : 5086 : + binlog_name_make(name, file_no); 1493 : : + 1494 : 5086 : + os_file_create_subdirs_if_needed(name); 1495 : : + 1496 : 5094 : +try_again: 1497 : 5094 : + fh = os_file_create( 1498 : : + innodb_data_file_key, name, 1499 : : + OS_FILE_CREATE, OS_DATA_FILE, srv_read_only_mode, &ret); 1500 : : + 1501 : 5094 : + if (!ret) { 1502 : 0 : + os_file_close(fh); 1503 : 0 : + return DB_ERROR; 1504 : : + } 1505 : : + 1506 : : + /* We created the binlog file and now write it full of zeros */ 1507 : 5094 : + if (!os_file_set_size(name, fh, 1508 : 5094 : + os_offset_t{size_in_pages} << ibb_page_size_shift) 1509 : : + ) { 1510 : : + char buf[MYSYS_STRERROR_SIZE]; 1511 : 8 : + ulong wait_sec= MY_WAIT_FOR_USER_TO_FIX_PANIC; 1512 : 8 : + DBUG_EXECUTE_IF("ib_alloc_file_disk_full", 1513 : : + wait_sec= 2;); 1514 : 8 : + my_strerror(buf, sizeof(buf), errno); 1515 : 8 : + sql_print_error("InnoDB: Unable to allocate file %s: \"%s\". " 1516 : : + "Waiting %lu seconds before trying again...", 1517 : : + name, buf, wait_sec); 1518 : 8 : + os_file_close(fh); 1519 : 8 : + os_file_delete(innodb_data_file_key, name); 1520 : 8 : + my_sleep(wait_sec * 1000000); 1521 : 8 : + goto try_again; 1522 : : + } 1523 : : + 1524 : : + /* 1525 : : + Enter an initial entry in the hash for this binlog tablespace file. 1526 : : + It will be later updated with the appropriate values when the file 1527 : : + first gets used and the header page is written. 1528 : : + */ 1529 : 5086 : + ibb_record_in_file_hash(file_no, ~(uint64_t)0,~(uint64_t)0, pins); 1530 : : + 1531 : 5086 : + binlog_page_fifo->create_tablespace(file_no, size_in_pages); 1532 : 5086 : + os_file_close(fh); 1533 : : + 1534 : 5086 : + return DB_SUCCESS; 1535 : : +} 1536 : : + 1537 : : + 1538 : : +/** 1539 : : + Write out a binlog record. 1540 : : + Split into chucks that each fit on a page. 1541 : : + The data for the record is provided by a class derived from chunk_data_base. 1542 : : + 1543 : : + As a special case, a record write of type FSP_BINLOG_TYPE_FILLER does not 1544 : : + write any record, but moves to the next tablespace and writes the initial 1545 : : + GTID state record, used for FLUSH BINARY LOGS. 1546 : : + 1547 : : + Returns a pair of {file_no, offset} marking the start of the record written. 1548 : : +*/ 1549 : : +std::pair 1550 : 159117 : +fsp_binlog_write_rec(chunk_data_base *chunk_data, mtr_t *mtr, byte chunk_type, 1551 : : + LF_PINS *pins) 1552 : : +{ 1553 : 159117 : + uint32_t page_size= (uint32_t)ibb_page_size; 1554 : 159117 : + uint32_t page_size_shift= ibb_page_size_shift; 1555 : 159117 : + const uint32_t page_end= page_size - BINLOG_PAGE_DATA_END; 1556 : 159117 : + uint32_t page_no= binlog_cur_page_no; 1557 : 159117 : + uint32_t page_offset= binlog_cur_page_offset; 1558 : 159117 : + fsp_binlog_page_entry *block= nullptr; 1559 : 159117 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 1560 : 159117 : + uint64_t pending_prev_end_offset= 0; 1561 : 159117 : + uint64_t start_file_no= 0; 1562 : 159117 : + uint64_t start_offset= 0; 1563 : : + 1564 : : + /* 1565 : : + Write out the event data in chunks of whatever size will fit in the current 1566 : : + page, until all data has been written. 1567 : : + */ 1568 : 159117 : + byte cont_flag= 0; 1569 : : + for (;;) { 1570 : 174508 : + if (page_offset == BINLOG_PAGE_DATA) { 1571 : 17667 : + ut_ad(!block); 1572 : 17667 : + uint32_t file_size_in_pages= binlog_page_fifo->size_in_pages(file_no); 1573 : 17667 : + if (UNIV_UNLIKELY(page_no >= file_size_in_pages)) { 1574 : : + /* 1575 : : + Signal to the pre-allocation thread that this tablespace has been 1576 : : + written full, so that it can be closed and a new one pre-allocated 1577 : : + in its place. Then wait for a new tablespace to be pre-allocated that 1578 : : + we can use. 1579 : : + 1580 : : + The normal case is that the next tablespace is already pre-allocated 1581 : : + and available; binlog tablespace N is active while (N+1) is being 1582 : : + pre-allocated. Only under extreme I/O pressure should we need to 1583 : : + stall here. 1584 : : + */ 1585 : 1395 : + ut_ad(!pending_prev_end_offset); 1586 : 1395 : + pending_prev_end_offset= page_no << page_size_shift; 1587 : 1395 : + mysql_mutex_lock(&active_binlog_mutex); 1588 : 1530 : + while (last_created_binlog_file_no <= file_no) { 1589 : 135 : + my_cond_wait(&active_binlog_cond, &active_binlog_mutex.m_mutex); 1590 : : + } 1591 : : + 1592 : 1395 : + ++file_no; 1593 : 1395 : + file_size_in_pages= binlog_page_fifo->size_in_pages(file_no); 1594 : 1395 : + binlog_cur_durable_offset[file_no & 3].store(0, std::memory_order_relaxed); 1595 : 1395 : + binlog_cur_end_offset[file_no & 3].store(0, std::memory_order_relaxed); 1596 : 1395 : + pthread_cond_signal(&active_binlog_cond); 1597 : 1395 : + mysql_mutex_unlock(&active_binlog_mutex); 1598 : 1395 : + binlog_cur_page_no= page_no= 0; 1599 : 1395 : + current_binlog_state_interval= 1600 : 1395 : + (uint64_t)(innodb_binlog_state_interval >> page_size_shift); 1601 : : + } 1602 : : + 1603 : : + /* Write the header page at the start of a binlog tablespace file. */ 1604 : 17667 : + if (page_no == 0) 1605 : : + { 1606 : : + /* Active is moving to next file, so check if oob refcount of previous 1607 : : + file is zero. 1608 : : + */ 1609 : 3231 : + if (UNIV_LIKELY(file_no > 0)) 1610 : 1395 : + ibb_file_hash.do_zero_refcnt_action(file_no - 1, pins, true); 1611 : : + 1612 : 3231 : + lsn_t start_lsn= log_get_lsn(); 1613 : 3231 : + bool err= ibb_write_header_page(mtr, file_no, file_size_in_pages, 1614 : : + start_lsn, 1615 : : + current_binlog_state_interval, pins); 1616 : 3231 : + ut_a(!err); 1617 : 3231 : + page_no= 1; 1618 : : + } 1619 : : + 1620 : : + /* Must be a power of two. */ 1621 : 17667 : + ut_ad(current_binlog_state_interval == 0 || 1622 : : + current_binlog_state_interval == 1623 : : + (uint64_t)1 << (63 - my_nlz(current_binlog_state_interval))); 1624 : : + 1625 : 17667 : + if (page_no == 1 || 1626 : 14436 : + 0 == (page_no & (current_binlog_state_interval - 1))) { 1627 : 4347 : + if (page_no == 1) { 1628 : : + bool err; 1629 : 3231 : + rpl_binlog_state_base *binlog_state= &binlog_full_state; 1630 : 3231 : + binlog_diff_state.reset_nolock(); 1631 : 5067 : + if (UNIV_UNLIKELY(file_no == 0 && page_no == 1) && 1632 : 1836 : + (binlog_full_state.count_nolock() == 1)) 1633 : : + { 1634 : : + /* 1635 : : + The gtid state written here includes the GTID for the event group 1636 : : + currently being written. This is precise when the event group 1637 : : + data begins before this point. If the event group happens to 1638 : : + start exactly on a binlog file boundary, it just means we will 1639 : : + have to read slightly more binlog data to find the starting point 1640 : : + of that GTID. 1641 : : + 1642 : : + But there is an annoying case if this is the very first binlog 1643 : : + file created (no migration from legacy binlog). If we start the 1644 : : + binlog with some GTID 0-1-1 and write the state "0-1-1" at the 1645 : : + start of the first file, then we will be unable to start 1646 : : + replicating from the GTID position "0-1-1", corresponding to the 1647 : : + *second* event group in the binlog. Because there will be no 1648 : : + slightly earlier point to start reading from! 1649 : : + 1650 : : + So we put a slightly awkward special case here to handle that: If 1651 : : + at the start of the first file we have a singleton gtid state 1652 : : + with seq_no=1, D-S-1, then it must be the very first GTID in the 1653 : : + entire binlog, so we write an *empty* gtid state that will always 1654 : : + allow to start replicating from the very start of the binlog. 1655 : : + 1656 : : + (If the user would explicitly set the seq_no of the very first 1657 : : + GTID in the binlog greater than 1, then starting from that GTID 1658 : : + position will still not be possible). 1659 : : + */ 1660 : : + rpl_gtid singleton_gtid; 1661 : 0 : + binlog_full_state.get_gtid_list_nolock(&singleton_gtid, 1); 1662 : 0 : + if (singleton_gtid.seq_no == 1) 1663 : 0 : + binlog_state= &binlog_diff_state; // Conveniently empty 1664 : : + } 1665 : 3231 : + err= binlog_gtid_state(binlog_state, mtr, block, page_no, 1666 : : + page_offset, file_no); 1667 : 3231 : + ut_a(!err); 1668 : 3231 : + ut_ad(block); 1669 : : + } else { 1670 : 1116 : + bool err= binlog_gtid_state(&binlog_diff_state, mtr, block, page_no, 1671 : : + page_offset, file_no); 1672 : 1116 : + ut_a(!err); 1673 : : + } 1674 : 4347 : + } else 1675 : 13320 : + block= binlog_page_fifo->create_page(file_no, page_no); 1676 : : + } else { 1677 : 156841 : + block= binlog_page_fifo->get_page(file_no, page_no); 1678 : : + } 1679 : : + 1680 : 174508 : + ut_ad(page_offset < page_end); 1681 : 174508 : + uint32_t page_remain= page_end - page_offset; 1682 : 174508 : + byte *ptr= page_offset + &block->page_buf()[0]; 1683 : 174508 : + if (page_remain < 4) { 1684 : : + /* Pad the remaining few bytes, and move to next page. */ 1685 : 17 : + if (UNIV_LIKELY(page_remain > 0)) 1686 : : + { 1687 : 17 : + memset(ptr, FSP_BINLOG_TYPE_FILLER, page_remain); 1688 : 17 : + fsp_log_binlog_write(mtr, block, file_no, page_no, page_offset, 1689 : : + page_remain); 1690 : : + } 1691 : 17 : + binlog_page_fifo->release_page_mtr(block, mtr); 1692 : 17 : + block= nullptr; 1693 : 17 : + ++page_no; 1694 : 17 : + page_offset= BINLOG_PAGE_DATA; 1695 : 17 : + DBUG_EXECUTE_IF("pause_binlog_write_after_release_page", 1696 : : + my_sleep(200000);); 1697 : 17 : + continue; 1698 : : + } 1699 : : + 1700 : 174491 : + if (UNIV_UNLIKELY(chunk_type == FSP_BINLOG_TYPE_FILLER)) 1701 : : + { 1702 : : + /* 1703 : : + Used for FLUSH BINARY LOGS, to move to the next tablespace and write 1704 : : + the initial GTID state record without writing any actual event data. 1705 : : + */ 1706 : 2266 : + break; 1707 : : + } 1708 : : + 1709 : 172225 : + if (start_offset == 0) 1710 : : + { 1711 : 156851 : + start_file_no= file_no; 1712 : 156851 : + start_offset= (page_no << page_size_shift) + page_offset; 1713 : : + } 1714 : 172225 : + page_remain-= 3; /* Type byte and 2-byte length. */ 1715 : : + std::pair size_last= 1716 : 172225 : + chunk_data->copy_data(ptr+3, page_remain); 1717 : 172225 : + uint32_t size= size_last.first; 1718 : 172225 : + ut_ad(size_last.second || size == page_remain); 1719 : 172225 : + ut_ad(size <= page_remain); 1720 : 172225 : + page_remain-= size; 1721 : 172225 : + byte last_flag= size_last.second ? FSP_BINLOG_FLAG_LAST : 0; 1722 : 172225 : + ptr[0]= chunk_type | cont_flag | last_flag; 1723 : 172225 : + ptr[1]= size & 0xff; 1724 : 172225 : + ptr[2]= (byte)(size >> 8); 1725 : 172225 : + ut_ad(size <= 0xffff); 1726 : : + 1727 : 172225 : + fsp_log_binlog_write(mtr, block, file_no, page_no, page_offset, size + 3); 1728 : 172225 : + cont_flag= FSP_BINLOG_FLAG_CONT; 1729 : 172225 : + if (page_remain == 0) { 1730 : 15814 : + binlog_page_fifo->release_page_mtr(block, mtr); 1731 : 15814 : + block= nullptr; 1732 : 15814 : + page_offset= BINLOG_PAGE_DATA; 1733 : 15814 : + ++page_no; 1734 : 15814 : + DBUG_EXECUTE_IF("pause_binlog_write_after_release_page", 1735 : : + if (!size_last.second) 1736 : : + my_sleep(200000); 1737 : : + ); 1738 : : + } else { 1739 : 156411 : + page_offset+= size+3; 1740 : : + } 1741 : 172225 : + if (size_last.second) 1742 : 156851 : + break; 1743 : 15374 : + ut_ad(!block); 1744 : 15374 : + if (UNIV_UNLIKELY(block != nullptr)) 1745 : : + { 1746 : : + /* 1747 : : + Defensive coding, just to not leave a page latch which would hang the 1748 : : + entire server hard. This code should not be reachable. 1749 : : + */ 1750 : 0 : + binlog_page_fifo->release_page_mtr(block, mtr); 1751 : 0 : + block= nullptr; 1752 : : + } 1753 : 15391 : + } 1754 : 159117 : + if (block) 1755 : 158677 : + binlog_page_fifo->release_page_mtr(block, mtr); 1756 : 159117 : + binlog_cur_page_no= page_no; 1757 : 159117 : + binlog_cur_page_offset= page_offset; 1758 : 159117 : + binlog_cur_end_offset[file_no & 3].store 1759 : 159117 : + (((uint64_t)page_no << page_size_shift) + page_offset, 1760 : : + std::memory_order_relaxed); 1761 : 159117 : + if (UNIV_UNLIKELY(pending_prev_end_offset != 0)) 1762 : : + { 1763 : 1395 : + mysql_mutex_lock(&binlog_durable_mutex); 1764 : 1395 : + mysql_mutex_lock(&active_binlog_mutex); 1765 : 1395 : + binlog_cur_end_offset[(file_no-1) & 3].store(pending_prev_end_offset, 1766 : : + std::memory_order_relaxed); 1767 : : + active_binlog_file_no.store(file_no, std::memory_order_release); 1768 : 1395 : + pthread_cond_signal(&active_binlog_cond); 1769 : 1395 : + mysql_mutex_unlock(&active_binlog_mutex); 1770 : 1395 : + mysql_mutex_unlock(&binlog_durable_mutex); 1771 : : + } 1772 : 159117 : + return {start_file_no, start_offset}; 1773 : : +} 1774 : : + 1775 : : + 1776 : : +/** 1777 : : + Implementation of FLUSH BINARY LOGS. 1778 : : + Truncate the current binlog tablespace, fill up the last page with dummy data 1779 : : + (if needed), write the current GTID state to the first page in the next 1780 : : + tablespace file (for DELETE_DOMAIN_ID). 1781 : : + 1782 : : + Relies on the server layer to prevent other binlog writes in parallel during 1783 : : + the operation. 1784 : : +*/ 1785 : : +bool 1786 : 430 : +fsp_binlog_flush() 1787 : : +{ 1788 : 430 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 1789 : 430 : + uint32_t page_no= binlog_cur_page_no; 1790 : 430 : + chunk_data_flush dummy_data; 1791 : 430 : + mtr_t mtr{nullptr}; 1792 : : + 1793 : 430 : + mysql_mutex_lock(&purge_binlog_mutex); 1794 : : + 1795 : 430 : + binlog_page_fifo->lock_wait_for_idle(); 1796 : 430 : + File fh= binlog_page_fifo->get_fh(file_no); 1797 : 430 : + if (fh == (File)-1) 1798 : : + { 1799 : 0 : + binlog_page_fifo->unlock(); 1800 : 0 : + mysql_mutex_unlock(&purge_binlog_mutex); 1801 : 0 : + return true; 1802 : : + } 1803 : : + 1804 : 430 : + if (my_chsize(fh, ((uint64_t)page_no + 1) << ibb_page_size_shift, 0, 1805 : : + MYF(MY_WME))) 1806 : : + { 1807 : 0 : + binlog_page_fifo->unlock(); 1808 : 0 : + mysql_mutex_unlock(&purge_binlog_mutex); 1809 : 0 : + return true; 1810 : : + } 1811 : : + /* 1812 : : + Sync the truncate to disk. This way, if we crash after this we are sure the 1813 : : + truncate has been effected so we do not put the filler record in what is 1814 : : + then the middle of the file. If we crash before the truncate is durable, we 1815 : : + just come up as if the flush has never happened. If we crash with the 1816 : : + truncate durable but without the filler record, that is not a problem, the 1817 : : + binlog file will just be shorter. 1818 : : + */ 1819 : 430 : + my_sync(fh, MYF(0)); 1820 : 430 : + binlog_page_fifo->unlock(); 1821 : : + 1822 : 430 : + LF_PINS *lf_pins= lf_hash_get_pins(&ibb_file_hash.hash); 1823 : 430 : + ut_a(lf_pins); 1824 : 430 : + uint32_t page_offset= binlog_cur_page_offset; 1825 : 430 : + if (page_offset > BINLOG_PAGE_DATA || 1826 : 0 : + page_offset < ibb_page_size - BINLOG_PAGE_DATA_END) 1827 : : + { 1828 : : + /* 1829 : : + If we are not precisely the end of a page, fill up that page with a dummy 1830 : : + record. Otherwise the zeros at the end of the page would be detected as 1831 : : + end-of-file of the entire binlog. 1832 : : + */ 1833 : 430 : + mtr.start(); 1834 : 430 : + fsp_binlog_write_rec(&dummy_data, &mtr, FSP_BINLOG_TYPE_DUMMY, lf_pins); 1835 : 430 : + mtr.commit(); 1836 : : + } 1837 : : + 1838 : 430 : + if (page_no + 1 < binlog_page_fifo->size_in_pages(file_no)) 1839 : : + { 1840 : 420 : + binlog_page_fifo->truncate_file_size(file_no, page_no + 1); 1841 : 420 : + size_t reclaimed= (binlog_page_fifo->size_in_pages(file_no) - (page_no + 1)) 1842 : 420 : + << ibb_page_size_shift; 1843 : 420 : + if (UNIV_LIKELY(total_binlog_used_size >= reclaimed)) 1844 : 420 : + total_binlog_used_size-= reclaimed; 1845 : : + else 1846 : 0 : + ut_ad(0); 1847 : : + } 1848 : : + 1849 : : + /* Flush out all pages in the (now filled-up) tablespace. */ 1850 : 430 : + binlog_page_fifo->flush_up_to(file_no, page_no); 1851 : : + 1852 : : + /* 1853 : : + Load the binlog GTID state from the server layer (in case it changed 1854 : : + due to FLUSH BINARY LOGS DELETE_DOMAIN_ID). 1855 : : + */ 1856 : 430 : + load_global_binlog_state(&binlog_full_state); 1857 : : + 1858 : 430 : + mysql_mutex_unlock(&purge_binlog_mutex); 1859 : : + 1860 : : + /* 1861 : : + Now get a new GTID state record written to the next binlog tablespace. 1862 : : + This ensures that the new state (in case of DELETE_DOMAIN_ID) will be 1863 : : + persisted across a server restart. 1864 : : + */ 1865 : 430 : + mtr.start(); 1866 : 430 : + fsp_binlog_write_rec(&dummy_data, &mtr, FSP_BINLOG_TYPE_FILLER, lf_pins); 1867 : 430 : + mtr.commit(); 1868 : 430 : + lf_hash_put_pins(lf_pins); 1869 : 430 : + log_buffer_flush_to_disk(srv_flush_log_at_trx_commit & 1); 1870 : 430 : + ibb_pending_lsn_fifo.add_to_fifo(mtr.commit_lsn(), file_no+1, 1871 : 430 : + binlog_cur_end_offset[(file_no + 1) & 3].load(std::memory_order_relaxed)); 1872 : : + 1873 : 430 : + return false; 1874 : 430 : +} 1875 : : + 1876 : : + 1877 : 3798 : +binlog_chunk_reader::binlog_chunk_reader(std::atomic *limit_offset_) 1878 : 3798 : + : s { 0, ~(uint64_t)0, 0, 0, 0, 0, FSP_BINLOG_TYPE_FILLER, false, false }, 1879 : 3798 : + stop_file_no(~(uint64_t)0), page_ptr(0), cur_block(0), page_buffer(nullptr), 1880 : 3798 : + limit_offset(limit_offset_), cur_file_handle((File)-1), 1881 : 3798 : + skipping_partial(false) 1882 : : +{ 1883 : : + /* Nothing else. */ 1884 : 3798 : +} 1885 : : + 1886 : : + 1887 : 3796 : +binlog_chunk_reader::~binlog_chunk_reader() 1888 : : +{ 1889 : 3796 : + release(); 1890 : 3796 : + if (cur_file_handle >= (File)0) 1891 : 2086 : + my_close(cur_file_handle, MYF(0)); 1892 : 3796 : +} 1893 : : + 1894 : : + 1895 : : +int 1896 : 0 : +binlog_chunk_reader::read_error_corruption(uint64_t file_no, uint64_t page_no, 1897 : : + const char *msg) 1898 : : +{ 1899 : 0 : + sql_print_error("InnoDB: Corrupt binlog found on page %" PRIu64 1900 : : + " in binlog number %" PRIu64 ": %s", page_no, file_no, msg); 1901 : 0 : + return -1; 1902 : : +} 1903 : : + 1904 : : + 1905 : : +/** 1906 : : + Obtain the data on the page currently pointed to by the chunk reader. The 1907 : : + page is either latched in the page fifo, or read from the file into the page 1908 : : + buffer. 1909 : : + 1910 : : + The code does a dirty read of active_binlog_file_no to determine if the page 1911 : : + is known to be available to read from the file, or if it should be looked up 1912 : : + in the buffer pool. After making the decision, another dirty read is done to 1913 : : + protect against the race where the active tablespace changes in the middle, 1914 : : + and if so the operation is re-tried. This is necessary since the binlog files 1915 : : + N and N-2 use the same tablespace id, so we must ensure we do not mistake a 1916 : : + page from N as belonging to N-2. 1917 : : +*/ 1918 : : +enum binlog_chunk_reader::chunk_reader_status 1919 : 58237 : +binlog_chunk_reader::fetch_current_page() 1920 : : +{ 1921 : 58237 : + ut_ad(!cur_block /* Must have no active page latch */); 1922 : 58237 : + uint64_t active2= active_binlog_file_no.load(std::memory_order_acquire); 1923 : : + for (;;) { 1924 : 58263 : + fsp_binlog_page_entry *block= nullptr; 1925 : 58263 : + uint64_t offset= (s.page_no << ibb_page_size_shift) | s.in_page_offset; 1926 : 58263 : + uint64_t active= active2; 1927 : : + uint64_t end_offset= 1928 : 58263 : + limit_offset[s.file_no & 3].load(std::memory_order_acquire); 1929 : : + /* 1930 : : + Can be different from end_offset if limit_offset is the 1931 : : + binlog_cur_durable_offset. 1932 : : + */ 1933 : : + uint64_t real_end_offset= 1934 : 58263 : + binlog_cur_end_offset[s.file_no & 3].load(std::memory_order_acquire); 1935 : 58263 : + if (s.file_no > active || UNIV_UNLIKELY(active == ~(uint64_t)0) 1936 : 58257 : + || UNIV_UNLIKELY(s.file_no > stop_file_no)) 1937 : : + { 1938 : 50 : + ut_ad(s.page_no == 1 || s.file_no > stop_file_no); 1939 : 50 : + ut_ad(s.in_page_offset == 0 || s.file_no > stop_file_no); 1940 : : + /* 1941 : : + Allow a reader that reached the very end of the active binlog file to 1942 : : + have moved ahead early to the start of the coming binlog file. 1943 : : + */ 1944 : 50 : + return CHUNK_READER_EOF; 1945 : : + } 1946 : : + 1947 : 58213 : + if (s.file_no + 1 >= active) { 1948 : : + /* Check if we should read from the buffer pool or from the file. */ 1949 : 30262 : + if (end_offset != ~(uint64_t)0 && offset < end_offset) 1950 : 26623 : + block= binlog_page_fifo->get_page(s.file_no, s.page_no); 1951 : 30262 : + active2= active_binlog_file_no.load(std::memory_order_acquire); 1952 : 30262 : + if (UNIV_UNLIKELY(active2 != active)) { 1953 : : + /* 1954 : : + The active binlog file changed while we were processing; we might 1955 : : + have gotten invalid end_offset or a buffer pool page from a wrong 1956 : : + tablespace. So just try again. 1957 : : + */ 1958 : 9 : + if (block) 1959 : 4 : + binlog_page_fifo->release_page(block); 1960 : 9 : + continue; 1961 : : + } 1962 : 30253 : + cur_end_offset= end_offset; 1963 : 30253 : + if (offset >= end_offset) { 1964 : 3484 : + ut_ad(!block); 1965 : 3484 : + if (s.file_no == active) { 1966 : : + /* Reached end of the currently active binlog file -> EOF. */ 1967 : 3466 : + return CHUNK_READER_EOF; 1968 : : + } 1969 : 18 : + ut_ad(s.file_no + 1 == active); 1970 : 18 : + if (offset < real_end_offset) 1971 : : + { 1972 : : + /* 1973 : : + Reached durable limit of active-1 _and_ not at the end of the 1974 : : + file where we should move to the next one. 1975 : : + */ 1976 : 1 : + return CHUNK_READER_EOF; 1977 : : + } 1978 : : + } 1979 : 26786 : + if (block) { 1980 : 10937 : + cur_block= block; 1981 : 10937 : + page_ptr= block->page_buf(); 1982 : 10937 : + return CHUNK_READER_FOUND; 1983 : : + } else { 1984 : : + /* Not in buffer pool, just read it from the file. */ 1985 : : + /* Fall through to read from file. */ 1986 : : + } 1987 : : + } 1988 : : + 1989 : : + /* Tablespace is not open, just read from the file. */ 1990 : 43800 : + if (cur_file_handle < (File)0) 1991 : : + { 1992 : : + char filename[OS_FILE_MAX_PATH]; 1993 : : + MY_STAT stat_buf; 1994 : : + 1995 : 8897 : + binlog_name_make(filename, s.file_no); 1996 : 8897 : + cur_file_handle= my_open(filename, O_RDONLY | O_BINARY, MYF(MY_WME)); 1997 : 8897 : + if (UNIV_UNLIKELY(cur_file_handle < (File)0)) { 1998 : 4 : + cur_file_handle= (File)-1; 1999 : 4 : + cur_file_length= ~(uint64_t)0; 2000 : 4 : + return CHUNK_READER_ERROR; 2001 : : + } 2002 : 8893 : + if (my_fstat(cur_file_handle, &stat_buf, MYF(0))) { 2003 : 0 : + my_error(ER_CANT_GET_STAT, MYF(0), filename, errno); 2004 : 0 : + my_close(cur_file_handle, MYF(0)); 2005 : 0 : + cur_file_handle= (File)-1; 2006 : 0 : + cur_file_length= ~(uint64_t)0; 2007 : 0 : + return CHUNK_READER_ERROR; 2008 : : + } 2009 : 8893 : + cur_file_length= stat_buf.st_size; 2010 : : + } 2011 : 43796 : + if (s.file_no + 1 >= active) 2012 : 15847 : + cur_end_offset= end_offset; 2013 : : + else 2014 : 27949 : + cur_end_offset= cur_file_length; 2015 : : + 2016 : 43796 : + if (offset >= cur_file_length) { 2017 : : + /* End of this file, move to the next one. */ 2018 : 11 : + goto_next_file: 2019 : 17 : + if (UNIV_UNLIKELY(s.file_no >= stop_file_no)) 2020 : 0 : + return CHUNK_READER_EOF; 2021 : 17 : + if (cur_file_handle >= (File)0) 2022 : : + { 2023 : 17 : + my_close(cur_file_handle, MYF(0)); 2024 : 17 : + cur_file_handle= (File)-1; 2025 : 17 : + cur_file_length= ~(uint64_t)0; 2026 : : + } 2027 : 17 : + ++s.file_no; 2028 : 17 : + s.page_no= 1; /* Skip the header page. */ 2029 : 17 : + continue; 2030 : : + } 2031 : : + 2032 : 43785 : + int res= crc32_pread_page(cur_file_handle, page_buffer, s.page_no, 2033 : : + MYF(MY_WME)); 2034 : 43785 : + if (res < 0) 2035 : 2 : + return CHUNK_READER_ERROR; 2036 : 43783 : + if (res == 0) 2037 : 6 : + goto goto_next_file; 2038 : 43777 : + page_ptr= page_buffer; 2039 : 43777 : + return CHUNK_READER_FOUND; 2040 : 26 : + } 2041 : : + /* NOTREACHED */ 2042 : : +} 2043 : : + 2044 : : + 2045 : : +int 2046 : 439486 : +binlog_chunk_reader::read_data(byte *buffer, int max_len, bool multipage) 2047 : : +{ 2048 : : + uint32_t size; 2049 : 439486 : + int sofar= 0; 2050 : : + 2051 : 478339 : +read_more_data: 2052 : 478339 : + if (max_len == 0) 2053 : 3 : + return sofar; 2054 : : + 2055 : 478336 : + if (!page_ptr) 2056 : : + { 2057 : 55480 : + enum chunk_reader_status res= fetch_current_page(); 2058 : 55480 : + if (res == CHUNK_READER_EOF) 2059 : : + { 2060 : 3517 : + if (s.in_record && s.file_no <= stop_file_no) 2061 : 0 : + return read_error_corruption(s.file_no, s.page_no, "binlog tablespace " 2062 : 0 : + "truncated in the middle of record"); 2063 : : + else 2064 : 3517 : + return 0; 2065 : : + } 2066 : 51963 : + else if (res == CHUNK_READER_ERROR) 2067 : 2 : + return -1; 2068 : : + } 2069 : : + 2070 : 474817 : + if (s.chunk_len == 0) 2071 : : + { 2072 : : + byte type; 2073 : : + /* 2074 : : + This code gives warning "comparison of unsigned expression in ‘< 0’ is 2075 : : + always false" when BINLOG_PAGE_DATA is 0. 2076 : : + 2077 : : + So use a static assert for now; if it ever triggers, replace it with this 2078 : : + code: 2079 : : + 2080 : : + if (s.in_page_offset < BINLOG_PAGE_DATA) 2081 : : + s.in_page_offset= BINLOG_PAGE_DATA; 2082 : : + */ 2083 : : + if (0) 2084 : : + static_assert(BINLOG_PAGE_DATA == 0, 2085 : : + "Replace static_assert with code from above comment"); 2086 : : + 2087 : : + /* Check for end-of-file. */ 2088 : 250169 : + if ((s.page_no << ibb_page_size_shift) + s.in_page_offset >= cur_end_offset) 2089 : 9298 : + return sofar; 2090 : : + 2091 : 240871 : + if (s.in_page_offset >= ibb_page_size - (BINLOG_PAGE_DATA_END + 3) || 2092 : 240871 : + page_ptr[s.in_page_offset] == FSP_BINLOG_TYPE_FILLER) 2093 : : + { 2094 : 0 : + ut_ad(s.in_page_offset >= ibb_page_size - BINLOG_PAGE_DATA_END || 2095 : : + page_ptr[s.in_page_offset] == FSP_BINLOG_TYPE_FILLER); 2096 : 0 : + goto go_next_page; 2097 : : + } 2098 : : + 2099 : 240871 : + type= page_ptr[s.in_page_offset]; 2100 : 240871 : + if (type == 0) 2101 : : + { 2102 : 0 : + ut_ad(0 /* Should have detected end-of-file on cur_end_offset. */); 2103 : 0 : + return 0; 2104 : : + } 2105 : : + 2106 : : + /* 2107 : : + Consistency check on the chunks. A record must consist in a sequence of 2108 : : + chunks of the same type, all but the first must have the 2109 : : + FSP_BINLOG_FLAG_BIT_CONT bit set, and the final one must have the 2110 : : + FSP_BINLOG_FLAG_BIT_LAST bit set. 2111 : : + */ 2112 : 240871 : + if (!s.in_record) 2113 : : + { 2114 : 215376 : + if (UNIV_UNLIKELY(type & FSP_BINLOG_FLAG_CONT) && !s.skip_current) 2115 : : + { 2116 : 466 : + if (skipping_partial) 2117 : : + { 2118 : 466 : + s.chunk_len= page_ptr[s.in_page_offset + 1] | 2119 : 466 : + ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); 2120 : 466 : + s.skip_current= true; 2121 : 466 : + goto skip_chunk; 2122 : : + } 2123 : : + else 2124 : 0 : + return read_error_corruption(s.file_no, s.page_no, "Binlog record " 2125 : 0 : + "starts with continuation chunk"); 2126 : : + } 2127 : : + } 2128 : : + else 2129 : : + { 2130 : 25495 : + if ((type ^ s.chunk_type) & FSP_BINLOG_TYPE_MASK) 2131 : : + { 2132 : : + /* 2133 : : + As a special case, we must allow a GTID state to appear in the 2134 : : + middle of a record. 2135 : : + */ 2136 : 3251 : + if (((uint64_t)1 << (type & FSP_BINLOG_TYPE_MASK)) & 2137 : : + ALLOWED_NESTED_RECORDS) 2138 : : + { 2139 : 3251 : + s.chunk_len= page_ptr[s.in_page_offset + 1] | 2140 : 3251 : + ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); 2141 : 3251 : + goto skip_chunk; 2142 : : + } 2143 : : + /* Chunk type changed in the middle. */ 2144 : 0 : + return read_error_corruption(s.file_no, s.page_no, "Binlog record missing " 2145 : 0 : + "end chunk"); 2146 : : + } 2147 : 22244 : + if (!(type & FSP_BINLOG_FLAG_CONT)) 2148 : : + { 2149 : : + /* START chunk without END chunk. */ 2150 : 0 : + return read_error_corruption(s.file_no, s.page_no, "Binlog record missing " 2151 : 0 : + "end chunk"); 2152 : : + } 2153 : : + } 2154 : : + 2155 : 237154 : + s.skip_current= false; 2156 : 237154 : + s.chunk_type= type; 2157 : 237154 : + s.in_record= true; 2158 : 237154 : + s.rec_start_file_no= s.file_no; 2159 : 237154 : + s.chunk_len= page_ptr[s.in_page_offset + 1] | 2160 : 237154 : + ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); 2161 : 237154 : + s.chunk_read_offset= 0; 2162 : : + } 2163 : : + 2164 : : + /* Now we have a chunk available to read data from. */ 2165 : 461802 : + ut_ad(s.chunk_read_offset < s.chunk_len); 2166 : 461802 : + if (s.skip_current && 2167 : 34611 : + (s.chunk_read_offset > 0 || (s.chunk_type & FSP_BINLOG_FLAG_CONT))) 2168 : : + { 2169 : : + /* 2170 : : + Skip initial continuation chunks. 2171 : : + Used to be able to start reading potentially in the middle of a record, 2172 : : + ie. at a GTID state point. 2173 : : + */ 2174 : 34611 : + s.chunk_read_offset= s.chunk_len; 2175 : : + } 2176 : : + else 2177 : : + { 2178 : 427191 : + size= std::min((uint32_t)max_len, s.chunk_len - s.chunk_read_offset); 2179 : 427191 : + memcpy(buffer, page_ptr + s.in_page_offset + 3 + s.chunk_read_offset, size); 2180 : 427191 : + buffer+= size; 2181 : 427191 : + s.chunk_read_offset+= size; 2182 : 427191 : + max_len-= size; 2183 : 427191 : + sofar+= size; 2184 : : + } 2185 : : + 2186 : 461802 : + if (s.chunk_len > s.chunk_read_offset) 2187 : : + { 2188 : 224648 : + ut_ad(max_len == 0 /* otherwise would have read more */); 2189 : 224648 : + return sofar; 2190 : : + } 2191 : : + 2192 : : + /* We have read all of the chunk. Move to next chunk or end of the record. */ 2193 : 237154 : +skip_chunk: 2194 : 240871 : + s.in_page_offset+= 3 + s.chunk_len; 2195 : 240871 : + s.chunk_len= 0; 2196 : 240871 : + s.chunk_read_offset= 0; 2197 : : + 2198 : 240871 : + if (s.chunk_type & FSP_BINLOG_FLAG_LAST) 2199 : : + { 2200 : 214968 : + s.in_record= false; /* End of record. */ 2201 : 214968 : + s.skip_current= false; 2202 : : + } 2203 : : + 2204 : 240871 : + if (s.in_page_offset >= ibb_page_size - (BINLOG_PAGE_DATA_END + 3) && 2205 : 23168 : + (s.page_no << ibb_page_size_shift) + s.in_page_offset < cur_end_offset) 2206 : : + { 2207 : 23164 : +go_next_page: 2208 : : + /* End of page reached, move to the next page. */ 2209 : 23164 : + ++s.page_no; 2210 : 23164 : + page_ptr= nullptr; 2211 : 23164 : + if (cur_block) 2212 : : + { 2213 : 246 : + binlog_page_fifo->release_page(cur_block); 2214 : 246 : + cur_block= nullptr; 2215 : : + } 2216 : 23164 : + s.in_page_offset= 0; 2217 : : + 2218 : 23164 : + if (cur_file_handle >= (File)0 && 2219 : 23080 : + (s.page_no << ibb_page_size_shift) >= cur_file_length) 2220 : : + { 2221 : : + /* Move to the next file. */ 2222 : 1859 : + my_close(cur_file_handle, MYF(0)); 2223 : 1859 : + cur_file_handle= (File)-1; 2224 : 1859 : + cur_file_length= ~(uint64_t)0; 2225 : 1859 : + ++s.file_no; 2226 : 1859 : + s.page_no= 1; /* Skip the header page. */ 2227 : : + } 2228 : : + } 2229 : : + 2230 : 240871 : + if (sofar > 0 && (!multipage || !s.in_record)) 2231 : 202018 : + return sofar; 2232 : : + 2233 : 38853 : + goto read_more_data; 2234 : : +} 2235 : : + 2236 : : + 2237 : : +int 2238 : 75 : +binlog_chunk_reader::find_offset_in_page(uint32_t off) 2239 : : +{ 2240 : 75 : + if (!page_ptr) 2241 : : + { 2242 : 75 : + enum chunk_reader_status res= fetch_current_page(); 2243 : 75 : + if (res == CHUNK_READER_EOF) 2244 : 0 : + return 0; 2245 : 75 : + else if (res == CHUNK_READER_ERROR) 2246 : 4 : + return -1; 2247 : : + } 2248 : : + 2249 : : + /* 2250 : : + Skip ahead in the page until we come to the first chunk boundary that 2251 : : + is at or later than the requested offset. 2252 : : + */ 2253 : 71 : + s.in_page_offset= 0; 2254 : 71 : + s.chunk_len= 0; 2255 : 71 : + s.chunk_read_offset= 0; 2256 : 71 : + s.chunk_type= FSP_BINLOG_TYPE_FILLER; 2257 : 71 : + s.skip_current= 0; 2258 : 71 : + s.in_record= 0; 2259 : 71 : + while (s.in_page_offset < off && 2260 : 209 : + s.in_page_offset < cur_end_offset && 2261 : 140 : + s.in_page_offset < ibb_page_size) 2262 : : + { 2263 : 140 : + byte type= page_ptr[s.in_page_offset]; 2264 : 140 : + if (type == 0 || type == FSP_BINLOG_TYPE_FILLER) 2265 : : + break; 2266 : 138 : + uint32_t chunk_len= page_ptr[s.in_page_offset + 1] | 2267 : 138 : + ((uint32_t)page_ptr[s.in_page_offset + 2] << 8); 2268 : 138 : + s.in_page_offset+= std::min(3 + chunk_len, (uint32_t)ibb_page_size); 2269 : : + } 2270 : 71 : + return 0; 2271 : : +} 2272 : : + 2273 : : + 2274 : : +/** 2275 : : + Read the header page of the current binlog file_no. 2276 : : + Returns: 2277 : : + 1 Header page found and returned. 2278 : : + 0 EOF, no header page found (ie. file is empty / nothing is durable yet). 2279 : : + -1 Error. 2280 : : +*/ 2281 : : +int 2282 : 2682 : +binlog_chunk_reader::get_file_header(binlog_header_data *out_header) 2283 : : +{ 2284 : 2682 : + seek(current_file_no(), 0); 2285 : 2682 : + enum chunk_reader_status res= fetch_current_page(); 2286 : 2682 : + if (UNIV_UNLIKELY(res != CHUNK_READER_FOUND)) 2287 : 0 : + return res == CHUNK_READER_EOF ? 0 : -1; 2288 : 2682 : + fsp_binlog_extract_header_page(page_ptr, out_header); 2289 : 2682 : + if (out_header->is_invalid || out_header->is_empty) 2290 : 0 : + return -1; 2291 : 2682 : + return 1; 2292 : : +} 2293 : : + 2294 : : + 2295 : : +void 2296 : 52181 : +binlog_chunk_reader::restore_pos(binlog_chunk_reader::saved_position *pos) 2297 : : +{ 2298 : 52181 : + if (page_ptr && 2299 : 42248 : + !(pos->file_no == s.file_no && pos->page_no == s.page_no)) 2300 : : + { 2301 : : + /* Seek to a different page, release any current page. */ 2302 : 20004 : + if (cur_block) 2303 : : + { 2304 : 410 : + binlog_page_fifo->release_page(cur_block); 2305 : 410 : + cur_block= nullptr; 2306 : : + } 2307 : 20004 : + page_ptr= nullptr; 2308 : : + } 2309 : 52181 : + if (cur_file_handle != (File)-1 && pos->file_no != s.file_no) 2310 : : + { 2311 : : + /* Seek to a different file than currently open, close it. */ 2312 : 4929 : + my_close(cur_file_handle, MYF(0)); 2313 : 4929 : + cur_file_handle= (File)-1; 2314 : 4929 : + cur_file_length= ~(uint64_t)0; 2315 : : + } 2316 : 52181 : + s= *pos; 2317 : 52181 : +} 2318 : : + 2319 : : + 2320 : : +void 2321 : 39174 : +binlog_chunk_reader::seek(uint64_t file_no, uint64_t offset) 2322 : : +{ 2323 : 39174 : + saved_position pos { 2324 : 39174 : + file_no, ~(uint64_t)0, (uint32_t)(offset >> ibb_page_size_shift), 2325 : 39174 : + (uint32_t)(offset & (ibb_page_size - 1)), 2326 : 39174 : + 0, 0, FSP_BINLOG_TYPE_FILLER, false, false }; 2327 : 39174 : + restore_pos(&pos); 2328 : 39174 : +} 2329 : : + 2330 : : + 2331 : 55039 : +void binlog_chunk_reader::release(bool release_file_page) 2332 : : +{ 2333 : 55039 : + if (cur_block) 2334 : : + { 2335 : 10281 : + binlog_page_fifo->release_page(cur_block); 2336 : 10281 : + cur_block= nullptr; 2337 : 10281 : + page_ptr= nullptr; 2338 : : + } 2339 : 44758 : + else if (release_file_page) 2340 : : + { 2341 : : + /* 2342 : : + For when we reach EOF while reading from the file. We need to re-read 2343 : : + the page from the file in this case on next read, as data might be added 2344 : : + to the page. 2345 : : + */ 2346 : 3934 : + page_ptr= nullptr; 2347 : : + } 2348 : 55039 : +} 2349 : : + 2350 : : + 2351 : 28357 : +bool binlog_chunk_reader::data_available() 2352 : : +{ 2353 : 28357 : + if (!end_of_record()) 2354 : 0 : + return true; 2355 : 28357 : + uint64_t active= active_binlog_file_no.load(std::memory_order_acquire); 2356 : 28357 : + if (UNIV_UNLIKELY(active == ~(uint64_t)0)) 2357 : 0 : + return false; 2358 : : + uint64_t end_offset; 2359 : : + for (;;) 2360 : : + { 2361 : 28357 : + if (active > s.file_no + 1) 2362 : 487 : + return true; 2363 : 55740 : + end_offset= limit_offset[s.file_no & 3].load(std::memory_order_acquire); 2364 : 27870 : + uint64_t active2= active_binlog_file_no.load(std::memory_order_acquire); 2365 : 27870 : + if (active2 == active) 2366 : 27870 : + break; 2367 : : + /* Active moved while we were checking, try again. */ 2368 : 0 : + active= active2; 2369 : 0 : + } 2370 : 27870 : + uint64_t offset= (s.page_no << ibb_page_size_shift) | s.in_page_offset; 2371 : 27870 : + if (offset < end_offset) 2372 : 9462 : + return true; 2373 : : + 2374 : 18408 : + ut_ad(s.file_no + 1 == active || s.file_no == active); 2375 : 18408 : + ut_ad(offset == end_offset || (offset == ibb_page_size && end_offset == 0)); 2376 : 18408 : + return false; 2377 : : +} 2378 : : + 2379 : : + 2380 : : +bool 2381 : 21754 : +binlog_chunk_reader::is_before_pos(uint64_t file_no, uint64_t offset) 2382 : : +{ 2383 : 21754 : + if (s.file_no < file_no) 2384 : 853 : + return true; 2385 : 20901 : + if (s.file_no > file_no) 2386 : 0 : + return false; 2387 : 20901 : + uint64_t own_offset= (s.page_no << ibb_page_size_shift) | s.in_page_offset; 2388 : 20901 : + if (own_offset < offset) 2389 : 7905 : + return true; 2390 : 12996 : + return false; 2391 : : +} ===== File: storage/innobase/handler/ha_innodb.cc ===== 30 : : 31 : : #define MYSQL_SERVER 32 : : #include "univ.i" 33 : : +#include "my_bit.h" 34 : : 35 : : /* Include necessary SQL headers */ 36 : : #include "ha_prototypes.h" 108 : : #include "fil0pagecompress.h" 109 : : #include "ut0mem.h" 110 : : #include "row0ext.h" 111 : : +#include "innodb_binlog.h" 112 : : 113 : : #include "lz4.h" 114 : : #include "lzo/lzo1x.h" 532 : : mysql_pfs_key_t lock_wait_mutex_key; 533 : : mysql_pfs_key_t trx_sys_mutex_key; 534 : : mysql_pfs_key_t srv_threads_mutex_key; 535 : : +mysql_pfs_key_t fsp_active_binlog_mutex_key; 536 : : +mysql_pfs_key_t fsp_binlog_durable_mutex_key; 537 : : +mysql_pfs_key_t fsp_binlog_durable_cond_key; 538 : : +mysql_pfs_key_t fsp_purge_binlog_mutex_key; 539 : : +mysql_pfs_key_t fsp_page_fifo_mutex_key; 540 : : +mysql_pfs_key_t ibb_xid_hash_mutex_key; 541 : : 542 : : /* all_innodb_mutexes array contains mutexes that are 543 : : performance schema instrumented if "UNIV_PFS_MUTEX" 2474 : 532 : return(ut_str_sql_format(buf_tmp, buf_tmp_used, buf, buf_size)); 2475 : : } 2476 : : 2477 : : /*********************************************************************//** 2478 : : Compute the next autoinc value. 2479 : : 2516 : : operation. The snippet below calculates the product of two numbers 2517 : : and detects an unsigned integer overflow: 2518 : : */ 2519 : 829756 : + unsigned int m= my_nlz(need); 2520 : 829756 : + unsigned int n= my_nlz(step); 2521 : 829756 : if (m + n <= 8 * sizeof(ulonglong) - 2) { 2522 : : // The bit width of the original values is too large, 2523 : : // therefore we are guaranteed to get an overflow. 2703 : : { 2704 : 1487501 : trx->is_registered= false; 2705 : 1487501 : trx->active_commit_ordered= false; 2706 : 1487501 : + trx->active_prepare= false; 2707 : 1487501 : } 2708 : : 2709 : : /** 3780 : 0 : DBUG_RETURN(HA_ERR_INITIALIZATION); 3781 : : } 3782 : : 3783 : 11299 : + if (innodb_binlog_state_interval == 0 || 3784 : 11299 : + innodb_binlog_state_interval != 3785 : 11299 : + (ulonglong)1 << (63 - my_nlz(innodb_binlog_state_interval)) || 3786 : 11299 : + innodb_binlog_state_interval % (ulonglong)ibb_page_size) { 3787 : 0 : + sql_print_error("InnoDB: innodb_binlog_state_interval must be " 3788 : : + "a power-of-two multiple of the innodb binlog " 3789 : : + "page size=%lu KiB", ibb_page_size >> 10); 3790 : 0 : + DBUG_RETURN(HA_ERR_INITIALIZATION); 3791 : : + } 3792 : : + 3793 : : #ifdef _WIN32 3794 : : if (!is_filename_allowed(srv_buf_dump_filename, 3795 : : strlen(srv_buf_dump_filename), false)) 4038 : 11023 : } 4039 : : 4040 : : 4041 : 556 : +static binlog_file_entry *innodb_get_binlog_file_list(MEM_ROOT *mem_root) 4042 : : +{ 4043 : : + uint64_t first, last; 4044 : 556 : + if (innodb_find_binlogs(&first, &last)) 4045 : 0 : + return nullptr; 4046 : : + binlog_file_entry *list; 4047 : 556 : + binlog_file_entry **next_ptr= &list; 4048 : 3003 : + for (uint64_t i= first; i <= last; ++i) 4049 : : + { 4050 : 2447 : + binlog_file_entry *e= (binlog_file_entry *)alloc_root(mem_root, sizeof(*e)); 4051 : 2447 : + if (!e) 4052 : 0 : + return nullptr; 4053 : : + char name_buf[OS_FILE_MAX_PATH]; 4054 : 2447 : + binlog_name_make(name_buf, i); 4055 : 2447 : + e->name.length= strlen(name_buf); 4056 : 2447 : + char *str= static_cast(alloc_root(mem_root, e->name.length + 1)); 4057 : 2447 : + if (!str) 4058 : 0 : + return nullptr; 4059 : 2447 : + strcpy(str, name_buf); 4060 : 2447 : + e->name.str= str; 4061 : 2447 : + *next_ptr= e; 4062 : 2447 : + next_ptr= &(e->next); 4063 : : + } 4064 : 556 : + *next_ptr= nullptr; 4065 : 556 : + return list; 4066 : : +} 4067 : : + 4068 : : + 4069 : : +static bool 4070 : 430 : +innodb_binlog_flush() 4071 : : +{ 4072 : 430 : + return fsp_binlog_flush(); 4073 : : +} 4074 : : + 4075 : : + 4076 : : /** Initialize the InnoDB storage engine plugin. 4077 : : @param[in,out] p InnoDB handlerton 4078 : : @return error code 4146 : 11311 : = innodb_prepare_commit_versioned; 4147 : : 4148 : 11311 : innobase_hton->update_optimizer_costs= innobase_update_optimizer_costs; 4149 : 11311 : + innobase_hton->binlog_init= innodb_binlog_init; 4150 : 11311 : + innobase_hton->set_binlog_max_size= ibb_set_max_size; 4151 : 11311 : + innobase_hton->binlog_write_direct_ordered= 4152 : : + innobase_binlog_write_direct_ordered; 4153 : 11311 : + innobase_hton->binlog_write_direct= innobase_binlog_write_direct; 4154 : 11311 : + innobase_hton->binlog_group_commit_ordered= ibb_group_commit; 4155 : 11311 : + innobase_hton->binlog_oob_data_ordered= innodb_binlog_oob_ordered; 4156 : 11311 : + innobase_hton->binlog_oob_data= innodb_binlog_oob; 4157 : 11311 : + innobase_hton->binlog_savepoint_rollback= ibb_savepoint_rollback; 4158 : 11311 : + innobase_hton->binlog_oob_reset= innodb_reset_oob; 4159 : 11311 : + innobase_hton->binlog_oob_free= innodb_free_oob; 4160 : 11311 : + innobase_hton->binlog_write_xa_prepare_ordered= 4161 : : + ibb_write_xa_prepare_ordered; 4162 : 11311 : + innobase_hton->binlog_write_xa_prepare= ibb_write_xa_prepare; 4163 : 11311 : + innobase_hton->binlog_xa_rollback_ordered= 4164 : : + ibb_xa_rollback_ordered; 4165 : 11311 : + innobase_hton->binlog_xa_rollback= ibb_xa_rollback; 4166 : 11311 : + innobase_hton->binlog_unlog= ibb_binlog_unlog; 4167 : 11311 : + innobase_hton->get_binlog_reader= innodb_get_binlog_reader; 4168 : 11311 : + innobase_hton->get_binlog_file_list= innodb_get_binlog_file_list; 4169 : 11311 : + innobase_hton->get_filename= ibb_get_filename; 4170 : 11311 : + innobase_hton->binlog_status= innodb_binlog_status; 4171 : 11311 : + innobase_hton->binlog_flush= innodb_binlog_flush; 4172 : 11311 : + innobase_hton->binlog_get_init_state= innodb_binlog_get_init_state; 4173 : 11311 : + innobase_hton->reset_binlogs= innodb_reset_binlogs; 4174 : 11311 : + innobase_hton->binlog_purge= innodb_binlog_purge; 4175 : : 4176 : 11311 : innodb_remember_check_sysvar_funcs(); 4177 : : 4469 : 391750 : DBUG_ASSERT(all || 4470 : : (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN))); 4471 : : 4472 : 391750 : trx->active_commit_ordered = true; 4473 : 391750 : + innobase_commit_ordered_2(trx, thd); 4474 : : 4475 : 391750 : DBUG_VOID_RETURN; 4476 : 391750 : } 17287 : : case TRX_STATE_ACTIVE: 17288 : 947772 : trx->xid= *thd->get_xid(); 17289 : 947769 : if (prepare_trx) 17290 : : + { 17291 : 290316 : trx_prepare_for_mysql(trx); 17292 : 290318 : + trx->active_prepare= true; 17293 : : + } 17294 : : else 17295 : : { 17296 : 657453 : lock_unlock_table_autoinc(trx); 19987 : : "Shrink the temporary tablespace", 19988 : : NULL, innodb_trunc_temp_space_update, false); 19989 : : 19990 : : +static MYSQL_SYSVAR_ULONGLONG(binlog_state_interval, 19991 : : + innodb_binlog_state_interval, 19992 : : + PLUGIN_VAR_RQCMDARG | PLUGIN_VAR_READONLY, 19993 : : + "Interval (in bytes) at which to write the GTID binlog state to binlog " 19994 : : + "files to speed up GTID lookups. Must be a multiple of the binlog page " 19995 : : + "size (16384 bytes)", 19996 : : + NULL, NULL, 2*1024*1024, 19997 : : + 32768, ULONGLONG_MAX, 0); 19998 : : + 19999 : : static struct st_mysql_sys_var* innobase_system_variables[]= { 20000 : : MYSQL_SYSVAR(autoextend_increment), 20001 : : MYSQL_SYSVAR(buffer_pool_size), 20172 : : MYSQL_SYSVAR(background_thread), 20173 : : MYSQL_SYSVAR(encrypt_temporary_tables), 20174 : : MYSQL_SYSVAR(truncate_temporary_tablespace_now), 20175 : : + MYSQL_SYSVAR(binlog_state_interval), 20176 : : 20177 : : NULL 20178 : : }; ===== File: storage/innobase/handler/innodb_binlog.cc ===== 1 : : +/***************************************************************************** 2 : : + 3 : : +Copyright (c) 2024, Kristian Nielsen 4 : : + 5 : : +This program is free software; you can redistribute it and/or modify it under 6 : : +the terms of the GNU General Public License as published by the Free Software 7 : : +Foundation; version 2 of the License. 8 : : + 9 : : +This program is distributed in the hope that it will be useful, but WITHOUT 10 : : +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 : : +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 : : + 13 : : +You should have received a copy of the GNU General Public License along with 14 : : +this program; if not, write to the Free Software Foundation, Inc., 15 : : +51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA 16 : : + 17 : : +*****************************************************************************/ 18 : : + 19 : : +/**************************************************//** 20 : : +@file handler/innodb_binlog.cc 21 : : +InnoDB implementation of binlog. 22 : : +*******************************************************/ 23 : : + 24 : : +/* 25 : : + Need MYSQL_SERVER defined to be able to use THD_ENTER_COND from sql_class.h 26 : : + to make my_cond_wait() killable. 27 : : +*/ 28 : : +#define MYSQL_SERVER 1 29 : : +#include 30 : : +#include "sql_class.h" 31 : : + 32 : : +#include "innodb_binlog.h" 33 : : +#include "mtr0log.h" 34 : : +#include "fsp0fsp.h" 35 : : +#include "trx0trx.h" 36 : : +#include "log0log.h" 37 : : +#include "small_vector.h" 38 : : + 39 : : +#include "mysys_err.h" 40 : : +#include "my_compr_int.h" 41 : : +#include "rpl_gtid_base.h" 42 : : +#include "handler_binlog_reader.h" 43 : : +#include "log.h" 44 : : + 45 : : + 46 : : +class ibb_xid_hash; 47 : : + 48 : : + 49 : : +static int innodb_binlog_inited= 0; 50 : : + 51 : : +pending_lsn_fifo ibb_pending_lsn_fifo; 52 : : +uint32_t innodb_binlog_size_in_pages; 53 : : +const char *innodb_binlog_directory; 54 : : + 55 : : +/** Current write position in active binlog file. */ 56 : : +uint32_t binlog_cur_page_no; 57 : : +uint32_t binlog_cur_page_offset; 58 : : + 59 : : +/** 60 : : + Server setting for how often to dump a (differential) binlog state at the 61 : : + start of the page, to speed up finding the initial GTID position, read-only. 62 : : +*/ 63 : : +ulonglong innodb_binlog_state_interval; 64 : : + 65 : : +/** Binlog state of the engine binlog. */ 66 : : +rpl_binlog_state_base binlog_full_state; 67 : : +/** 68 : : + Differential binlog state in the currently active binlog tablespace, relative 69 : : + to the state at the start. 70 : : +*/ 71 : : +rpl_binlog_state_base binlog_diff_state; 72 : : + 73 : : +static std::thread binlog_prealloc_thr_obj; 74 : : +static bool prealloc_thread_end= false; 75 : : + 76 : : +/** 77 : : + Mutex around purge operations, including earliest_binlog_file_no and 78 : : + total_binlog_used_size. 79 : : +*/ 80 : : +mysql_mutex_t purge_binlog_mutex; 81 : : + 82 : : +/** The earliest binlog tablespace file. Used in binlog purge. */ 83 : : +static uint64_t earliest_binlog_file_no; 84 : : + 85 : : +/** 86 : : + The total space in use by binlog tablespace files. Maintained in-memory to 87 : : + not have to stat(2) every file for every new binlog tablespace allocated in 88 : : + case of --max-binlog-total-size. 89 : : + 90 : : + Initialized at server startup (and in RESET MASTER), and updated as binlog 91 : : + files are pre-allocated and purged. 92 : : +*/ 93 : : +size_t total_binlog_used_size; 94 : : + 95 : : +static bool purge_warning_given= false; 96 : : + 97 : : +/** References to pending XA PREPARED transactions in the binlog. */ 98 : : +ibb_xid_hash *ibb_xa_xid_hash; 99 : : + 100 : : +#ifdef UNIV_PFS_THREAD 101 : : +mysql_pfs_key_t binlog_prealloc_thread_key; 102 : : +#endif 103 : : + 104 : : + 105 : : +/** 106 : : + Structure holding context for out-of-band chunks of binlogged event group. 107 : : +*/ 108 : : +struct binlog_oob_context { 109 : : + struct savepoint; 110 : : + /* 111 : : + Structure used to encapsulate the data to be binlogged in an out-of-band 112 : : + chunk, for use by fsp_binlog_write_rec(). 113 : : + */ 114 : : + struct chunk_data_oob : public chunk_data_base { 115 : : + /* 116 : : + Need room for 5 numbers: 117 : : + node index 118 : : + left child file_no 119 : : + left child offset 120 : : + right child file_no 121 : : + right child offset 122 : : + */ 123 : : + static constexpr uint32_t max_buffer= 5*COMPR_INT_MAX64; 124 : : + uint64_t sofar; 125 : : + uint64_t main_len; 126 : : + const byte *main_data; 127 : : + uint32_t header_len; 128 : : + byte header_buf[max_buffer]; 129 : : + 130 : : + chunk_data_oob(uint64_t idx, 131 : : + uint64_t left_file_no, uint64_t left_offset, 132 : : + uint64_t right_file_no, uint64_t right_offset, 133 : : + const byte *data, size_t data_len); 134 : 17295 : + virtual ~chunk_data_oob() {}; 135 : : + virtual std::pair copy_data(byte *p, uint32_t max_len) final; 136 : : + }; 137 : : + 138 : : + bool binlog_node(uint32_t node, uint64_t new_idx, 139 : : + uint32_t left_node, uint32_t right_node, 140 : : + chunk_data_oob *oob_data, LF_PINS *pins, mtr_t *mtr); 141 : : + bool create_stmt_start_point(); 142 : : + savepoint *create_savepoint(); 143 : : + void rollback_to_savepoint(savepoint *savepoint); 144 : : + void rollback_to_stmt_start(); 145 : : + 146 : : + /* 147 : : + Pending binlog write for the ibb_pending_lsn_fifo. 148 : : + pending_file_no is ~0 when no write is pending. 149 : : + */ 150 : : + uint64_t pending_file_no; 151 : : + uint64_t pending_offset; 152 : : + lsn_t pending_lsn; 153 : : + 154 : : + uint64_t first_node_file_no; 155 : : + uint64_t first_node_offset; 156 : : + LF_PINS *lf_pins; 157 : : + savepoint *stmt_start_point; 158 : : + savepoint *savepoint_stack; 159 : : + /* 160 : : + The secondary pointer is for when server layer binlogs both a 161 : : + non-transactional and a transactional oob stream. 162 : : + */ 163 : : + binlog_oob_context *secondary_ctx; 164 : : + uint32_t node_list_len; 165 : : + uint32_t node_list_alloc_len; 166 : : + /* 167 : : + Set if we incremented refcount in first_node_file_no, so we need to 168 : : + decrement again at commit record write or reset/rollback. 169 : : + */ 170 : : + bool pending_refcount; 171 : : + /* Set when the transaction is sealed after writing an XA PREPARE record. */ 172 : : + bool is_xa_prepared; 173 : : + /* 174 : : + The node_list contains the root of each tree in the forest of perfect 175 : : + binary trees. 176 : : + */ 177 : : +#ifdef _MSC_VER 178 : : +/* Flexible array member is not standard C++, disable compiler warning. */ 179 : : +#pragma warning(disable : 4200) 180 : : +#endif 181 : : + struct node_info { 182 : : + uint64_t file_no; 183 : : + uint64_t offset; 184 : : + uint64_t node_index; 185 : : + uint32_t height; 186 : : + } node_list []; 187 : : + 188 : : + /* Saved oob state for implementing ROLLBACK TO SAVEPOINT. */ 189 : : + struct savepoint { 190 : : + /* Maintain a stack of pending savepoints. */ 191 : : + savepoint *next; 192 : : + uint32_t node_list_len; 193 : : + uint32_t alloc_len; 194 : : + struct node_info node_list[]; 195 : : + }; 196 : : +}; 197 : : + 198 : : + 199 : : +/** 200 : : + A class for doing the post-order traversal of the forest of perfect binary 201 : : + trees that make up the out-of-band data for a commit record. 202 : : +*/ 203 : : +class innodb_binlog_oob_reader { 204 : : + enum oob_states { 205 : : + /* The initial state, about to visit the node for the first time. */ 206 : : + ST_initial, 207 : : + /* State of leaf node while traversing the prior trees in the forest. */ 208 : : + ST_traversing_prior_trees, 209 : : + /* State of non-leaf node while traversing its left sub-tree. */ 210 : : + ST_traversing_left_child, 211 : : + /* State of non-leaf node while traversing its right sub-tree. */ 212 : : + ST_traversing_right_child, 213 : : + /* State of node while reading out its data. */ 214 : : + ST_self 215 : : + }; 216 : : + 217 : : + /* 218 : : + Stack entry for one node currently taking part in post-order traversal. 219 : : + We maintain a stack of pending nodes during the traversal, as the traversal 220 : : + happens in a state machine rather than by recursion. 221 : : + */ 222 : : + struct stack_entry { 223 : : + /* Saved position after reading header. */ 224 : : + binlog_chunk_reader::saved_position saved_pos; 225 : : + /* The location of this node's OOB record. */ 226 : : + uint64_t file_no; 227 : : + uint64_t offset; 228 : : + /* Right child, to be traversed after left child. */ 229 : : + uint64_t right_file_no; 230 : : + uint64_t right_offset; 231 : : + /* Offset of real data in this node, after header. */ 232 : : + uint32_t header_len; 233 : : + /* Amount of data read into rd_buf, and amount used to parse header. */ 234 : : + uint32_t rd_buf_len; 235 : : + uint32_t rd_buf_sofar; 236 : : + /* Current state in post-order traversal state machine. */ 237 : : + enum oob_states state; 238 : : + /* Buffer for reading header. */ 239 : : + byte rd_buf[5*COMPR_INT_MAX64]; 240 : : + /* 241 : : + True when the node is reached using only left child pointers, false 242 : : + otherwise. Used to identify the left-most leaf in a tree which points to 243 : : + a prior tree that must be traversed first. 244 : : + */ 245 : : + bool is_leftmost; 246 : : + }; 247 : : + small_vectorstack; 248 : : + 249 : : + /* State machine current state. */ 250 : : + enum oob_states state; 251 : : + 252 : : +public: 253 : : + innodb_binlog_oob_reader(); 254 : : + ~innodb_binlog_oob_reader(); 255 : : + 256 : : + void start_traversal(uint64_t file_no, uint64_t offset); 257 : 33339 : + bool oob_traversal_done() { return stack.empty(); } 258 : : + int read_data(binlog_chunk_reader *chunk_rd, uchar *buf, int max_len); 259 : : + 260 : : +private: 261 : : + void push_state(enum oob_states state, uint64_t file_no, uint64_t offset, 262 : : + bool is_leftmost); 263 : : +}; 264 : : + 265 : : + 266 : : +class ha_innodb_binlog_reader : public handler_binlog_reader { 267 : : + enum reader_states { 268 : : + ST_read_next_event_group, ST_read_oob_data, ST_read_commit_record 269 : : + }; 270 : : + 271 : : + binlog_chunk_reader chunk_rd; 272 : : + innodb_binlog_oob_reader oob_reader; 273 : : + binlog_chunk_reader::saved_position saved_commit_pos; 274 : : + 275 : : + /* Out-of-band data to read after commit record, if any. */ 276 : : + uint64_t oob_count; 277 : : + uint64_t oob_last_file_no; 278 : : + uint64_t oob_last_offset; 279 : : + /* Any secondary out-of-band data to be also read. */ 280 : : + uint64_t oob_count2; 281 : : + uint64_t oob_last_file_no2; 282 : : + uint64_t oob_last_offset2; 283 : : + /* 284 : : + Originally requested starting file_no, from init_gtid_pos() or 285 : : + init_legacy_pos(). Or ~0 if none. 286 : : + */ 287 : : + uint64_t requested_file_no; 288 : : + /* Buffer to hold a page read directly from the binlog file. */ 289 : : + uchar *page_buf; 290 : : + /* Keep track of pending bytes in the rd_buf. */ 291 : : + uint32_t rd_buf_len; 292 : : + uint32_t rd_buf_sofar; 293 : : + /* State for state machine reading chunks one by one. */ 294 : : + enum reader_states state; 295 : : + 296 : : + /* Used to read the header of the commit record. */ 297 : : + byte rd_buf[5*COMPR_INT_MAX64]; 298 : : +private: 299 : : + int read_data(uchar *buf, uint32_t len); 300 : : + 301 : : +public: 302 : : + ha_innodb_binlog_reader(bool wait_durable, uint64_t file_no= 0, 303 : : + uint64_t offset= 0); 304 : : + ~ha_innodb_binlog_reader(); 305 : : + virtual int read_binlog_data(uchar *buf, uint32_t len) final; 306 : : + virtual bool data_available() final; 307 : : + virtual bool wait_available(THD *thd, const struct timespec *abstime) final; 308 : : + virtual int init_gtid_pos(THD *thd, slave_connection_state *pos, 309 : : + rpl_binlog_state_base *state) final; 310 : : + virtual int init_legacy_pos(THD *thd, const char *filename, 311 : : + ulonglong offset) final; 312 : : + virtual void enable_single_file() final; 313 : : + void seek_internal(uint64_t file_no, uint64_t offset); 314 : : +}; 315 : : + 316 : : + 317 : : +struct chunk_data_cache : public chunk_data_base { 318 : : + IO_CACHE *cache; 319 : : + binlog_oob_context *oob_ctx; 320 : : + my_off_t main_start; 321 : : + size_t main_remain; 322 : : + size_t gtid_remain; 323 : : + uint32_t header_remain; 324 : : + uint32_t header_sofar; 325 : : + byte header_buf[10*COMPR_INT_MAX64]; 326 : : + 327 : 138978 : + chunk_data_cache(IO_CACHE *cache_arg, 328 : : + handler_binlog_event_group_info *binlog_info) 329 : 277956 : + : cache(cache_arg), 330 : 138978 : + main_start(binlog_info->out_of_band_offset), 331 : 138978 : + main_remain((size_t)(binlog_info->gtid_offset - 332 : 138978 : + binlog_info->out_of_band_offset)), 333 : 138978 : + header_sofar(0) 334 : : + { 335 : 138978 : + size_t end_offset= (size_t)my_b_tell(cache); 336 : 138978 : + ut_ad(end_offset > binlog_info->out_of_band_offset); 337 : 138978 : + ut_ad(binlog_info->gtid_offset >= binlog_info->out_of_band_offset); 338 : 138978 : + ut_ad(end_offset >= binlog_info->gtid_offset); 339 : 138978 : + gtid_remain= end_offset - (size_t)binlog_info->gtid_offset; 340 : : + 341 : 138978 : + binlog_oob_context *c= 342 : : + static_cast(binlog_info->engine_ptr); 343 : 138978 : + unsigned char *p= header_buf; 344 : 138978 : + ut_ad(c); 345 : 138978 : + oob_ctx= c; 346 : 138978 : + if (UNIV_UNLIKELY(!c)) 347 : : + ; 348 : 138978 : + else if (UNIV_UNLIKELY(binlog_info->xa_xid != nullptr) && 349 : 68 : + !binlog_info->internal_xa) 350 : : + { 351 : : + /* 352 : : + For explicit user XA COMMIT, the commit record must point to the 353 : : + OOB data previously saved in XA PREPARE. 354 : : + */ 355 : 28 : + bool err= ibb_xa_xid_hash->run_on_xid(binlog_info->xa_xid, 356 : 196 : + [&p](const ibb_xid_hash::xid_elem *elem) -> bool { 357 : 28 : + if (UNIV_LIKELY(elem->oob_num_nodes > 0)) 358 : : + { 359 : 28 : + p= compr_int_write(p, elem->oob_num_nodes); 360 : 28 : + p= compr_int_write(p, elem->oob_first_file_no); 361 : 28 : + p= compr_int_write(p, elem->oob_first_offset); 362 : 28 : + p= compr_int_write(p, elem->oob_last_file_no); 363 : 28 : + p= compr_int_write(p, elem->oob_last_offset); 364 : 28 : + p= compr_int_write(p, 0); 365 : : + } 366 : : + else 367 : 0 : + p= compr_int_write(p, 0); 368 : 28 : + return false; 369 : : + }); 370 : : + /* 371 : : + The XID must always be found, else we have a serious 372 : : + inconsistency between the server layer and binlog state. 373 : : + In case of inconsistency, better crash than leave a corrupt 374 : : + binlog. 375 : : + */ 376 : 28 : + ut_a(!err); 377 : 28 : + ut_ad(binlog_info->engine_ptr2 == nullptr); 378 : 28 : + } 379 : 138950 : + else if (c->node_list_len) 380 : : + { 381 : : + /* 382 : : + Link to the out-of-band data. First store the number of nodes; then 383 : : + store 2 x 2 numbers of file_no/offset for the first and last node. 384 : : + 385 : : + There's a special case when we have to link two times out-of-band data, 386 : : + due to mixing non-transactional and transactional stuff. In that case, 387 : : + the non-transactional goes first. In the common case where there is no 388 : : + dual oob references, we just store a single 0 count. 389 : : + */ 390 : 712 : + binlog_oob_context *c2= c->secondary_ctx= 391 : 712 : + static_cast(binlog_info->engine_ptr2); 392 : 712 : + if (UNIV_UNLIKELY(c2 != nullptr) && c2->node_list_len) 393 : : + { 394 : 10 : + uint32_t last2= c2->node_list_len-1; 395 : 10 : + uint64_t num_nodes2= c2->node_list[last2].node_index + 1; 396 : 10 : + p= compr_int_write(p, num_nodes2); 397 : 10 : + p= compr_int_write(p, c2->first_node_file_no); 398 : 10 : + p= compr_int_write(p, c2->first_node_offset); 399 : 10 : + p= compr_int_write(p, c2->node_list[last2].file_no); 400 : 10 : + p= compr_int_write(p, c2->node_list[last2].offset); 401 : : + } 402 : : + 403 : 712 : + uint32_t last= c->node_list_len-1; 404 : 712 : + uint64_t num_nodes= c->node_list[last].node_index + 1; 405 : 712 : + p= compr_int_write(p, num_nodes); 406 : 712 : + p= compr_int_write(p, c->first_node_file_no); 407 : 712 : + p= compr_int_write(p, c->first_node_offset); 408 : 712 : + p= compr_int_write(p, c->node_list[last].file_no); 409 : 712 : + p= compr_int_write(p, c->node_list[last].offset); 410 : 712 : + if (UNIV_LIKELY(c2 == nullptr) || c2->node_list_len == 0) 411 : 702 : + p= compr_int_write(p, 0); 412 : : + } 413 : : + else 414 : : + { 415 : : + /* 416 : : + No out-of-band data, marked with a single 0 count for nodes and no 417 : : + first/last links. 418 : : + */ 419 : 138238 : + p= compr_int_write(p, 0); 420 : : + } 421 : 138978 : + header_remain= (uint32_t)(p - header_buf); 422 : 138978 : + ut_ad((size_t)(p - header_buf) <= sizeof(header_buf)); 423 : : + 424 : 138978 : + ut_ad (cache->pos_in_file <= binlog_info->out_of_band_offset); 425 : : + 426 : 138978 : + if (UNIV_UNLIKELY(binlog_info->internal_xa)) 427 : : + { 428 : : + /* 429 : : + Insert the XID for the internal 2-phase commit in the xid_hash, 430 : : + incrementing the reference count. This will ensure we hold on to 431 : : + the commit record until ibb_binlog_unlog() is called, at which point 432 : : + the other participating storage engine(s) have durably committed. 433 : : + */ 434 : 40 : + bool err= ibb_xa_xid_hash->add_xid(binlog_info->xa_xid, c); 435 : 40 : + ut_a(!err); 436 : : + } 437 : : + 438 : : + /* Start with the GTID event, which is put at the end of the IO_CACHE. */ 439 : 138978 : + my_bool res= reinit_io_cache(cache, READ_CACHE, binlog_info->gtid_offset, 0, 0); 440 : 138978 : + ut_a(!res); 441 : 138978 : + } 442 : 138978 : + ~chunk_data_cache() { } 443 : : + 444 : 143478 : + virtual std::pair copy_data(byte *p, uint32_t max_len) final 445 : : + { 446 : 143478 : + uint32_t size= 0; 447 : : + 448 : : + /* Write header data, if any still available. */ 449 : 143478 : + if (header_remain > 0) 450 : : + { 451 : 138978 : + size= header_remain > max_len ? max_len : (uint32_t)header_remain; 452 : 138978 : + memcpy(p, header_buf + header_sofar, size); 453 : 138978 : + header_remain-= size; 454 : 138978 : + header_sofar+= size; 455 : 138978 : + max_len-= size; 456 : 138978 : + if (UNIV_UNLIKELY(max_len == 0)) 457 : : + { 458 : 3 : + ut_ad(gtid_remain + main_remain > 0); 459 : 3 : + return {size, false}; 460 : : + } 461 : : + } 462 : : + 463 : : + /* Write GTID data, if any still available. */ 464 : 143475 : + ut_ad(header_remain == 0); 465 : 143475 : + if (gtid_remain > 0) 466 : : + { 467 : 139287 : + uint32_t size2= gtid_remain > max_len ? max_len : (uint32_t)gtid_remain; 468 : 139287 : + int res2= my_b_read(cache, p + size, size2); 469 : 139287 : + ut_a(!res2 /* Reading from in-memory cache data cannot fail. */); 470 : 139287 : + gtid_remain-= size2; 471 : 139287 : + if (gtid_remain == 0) 472 : 138978 : + my_b_seek(cache, main_start); /* Move to read the rest of the events. */ 473 : 139287 : + max_len-= size2; 474 : 139287 : + size+= size2; 475 : 139287 : + if (max_len == 0) 476 : 316 : + return {size, gtid_remain + main_remain == 0}; 477 : : + } 478 : : + 479 : : + /* Write remaining data. */ 480 : 143159 : + ut_ad(gtid_remain == 0); 481 : 143159 : + if (main_remain == 0) 482 : : + { 483 : : + /* 484 : : + This means that only GTID data is present, eg. when the main data was 485 : : + already binlogged out-of-band. 486 : : + */ 487 : 909 : + ut_ad(size > 0); 488 : 909 : + return {size, true}; 489 : : + } 490 : 142250 : + uint32_t size2= main_remain > max_len ? max_len : (uint32_t)main_remain; 491 : 142250 : + int res2= my_b_read(cache, p + size, size2); 492 : 142250 : + ut_a(!res2); 493 : 142250 : + ut_ad(main_remain >= size2); 494 : 142250 : + main_remain-= size2; 495 : 142250 : + return {size + size2, main_remain == 0}; 496 : : + } 497 : : + 498 : : + /* 499 : : + To be called after binlogging is done, to decrement refcounts to any 500 : : + OOB nodes. 501 : : + */ 502 : 138978 : + void after_copy_data() 503 : : + { 504 : 138978 : + if (UNIV_LIKELY(oob_ctx != nullptr) && oob_ctx->pending_refcount) 505 : : + { 506 : 712 : + ibb_file_hash.oob_ref_dec(oob_ctx->first_node_file_no, oob_ctx->lf_pins); 507 : 712 : + oob_ctx->pending_refcount= false; 508 : 712 : + if (UNIV_UNLIKELY(oob_ctx->secondary_ctx != nullptr) && 509 : 10 : + oob_ctx->secondary_ctx->pending_refcount) 510 : : + { 511 : 10 : + ibb_file_hash.oob_ref_dec(oob_ctx->secondary_ctx->first_node_file_no, 512 : 10 : + oob_ctx->secondary_ctx->lf_pins); 513 : 10 : + oob_ctx->secondary_ctx->pending_refcount= false; 514 : : + } 515 : : + } 516 : : + 517 : 138978 : + } 518 : : +}; 519 : : + 520 : : + 521 : : +template 522 : : +struct chunk_data_from_buf : public chunk_data_base { 523 : : + static constexpr uint32_t bufsize= bufsize_; 524 : : + 525 : : + uint32_t data_remain; 526 : : + uint32_t data_sofar; 527 : : + byte buffer[bufsize]; 528 : : + 529 : 148 : + chunk_data_from_buf() : data_sofar(0) 530 : : + { 531 : : + /* data_remain must be initialized in derived class constructor. */ 532 : 148 : + } 533 : : + 534 : 148 : + virtual std::pair copy_data(byte *p, uint32_t max_len) final 535 : : + { 536 : 148 : + if (UNIV_UNLIKELY(data_remain <= 0)) 537 : 0 : + return {0, true}; 538 : 148 : + uint32_t size= data_remain > max_len ? max_len : data_remain; 539 : 148 : + memcpy(p, buffer + data_sofar, size); 540 : 148 : + data_remain-= size; 541 : 148 : + data_sofar+= size; 542 : 148 : + return {size, data_remain == 0}; 543 : : + } 544 : 148 : + ~chunk_data_from_buf() { } 545 : : +}; 546 : : + 547 : : + 548 : : +/** 549 : : + Record data for the XA prepare record. 550 : : + 551 : : + Size needed for the record data: 552 : : + 1 byte engine count. 553 : : + 4 bytes formatID 554 : : + 1 byte gtrid length 555 : : + 1 byte bqual length 556 : : + 128 bytes (max) gtrid and bqual strings. 557 : : + And last 5 compressed integers at most 5*COMPR_INT_MAX64: 558 : : + num_oob_nodes 559 : : + first_oob_file_no 560 : : + first_oob_offset 561 : : + last_oob_file_no 562 : : + last_oob_offset 563 : : +*/ 564 : : +static constexpr size_t ibb_prepare_record_max_size= 565 : : + 1 + 4 + 1 + 1 + 128 + 5*COMPR_INT_MAX64; 566 : : +struct chunk_data_xa_prepare : 567 : : + public chunk_data_from_buf { 568 : : + 569 : 52 : + chunk_data_xa_prepare(const XID *xid, uchar engine_count, 570 : : + binlog_oob_context *c) 571 : 52 : + { 572 : 52 : + buffer[0]= engine_count; 573 : 52 : + int4store(&buffer[1], xid->formatID); 574 : 52 : + ut_a(xid->gtrid_length >= 0 && xid->gtrid_length <= 64); 575 : 52 : + buffer[5]= (uchar)xid->gtrid_length; 576 : 52 : + ut_a(xid->bqual_length >= 0 && xid->bqual_length <= 64); 577 : 52 : + buffer[6]= (uchar)xid->bqual_length; 578 : 52 : + memcpy(&buffer[7], &xid->data[0], xid->gtrid_length + xid->bqual_length); 579 : 52 : + byte *p= &buffer[7] + xid->gtrid_length + xid->bqual_length; 580 : 52 : + uint32_t last= c->node_list_len-1; 581 : 52 : + p= compr_int_write(p, c->node_list[last].node_index + 1); 582 : 52 : + p= compr_int_write(p, c->first_node_file_no); 583 : 52 : + p= compr_int_write(p, c->first_node_offset); 584 : 52 : + p= compr_int_write(p, c->node_list[last].file_no); 585 : 52 : + p= compr_int_write(p, c->node_list[last].offset); 586 : 52 : + data_remain= static_cast(p - buffer); 587 : 52 : + } 588 : 52 : + ~chunk_data_xa_prepare() { } 589 : : +}; 590 : : + 591 : : + 592 : : +/** 593 : : + Record data for the XA COMMIT or XA ROLLBACK record. 594 : : + 595 : : + Size needed for the record data: 596 : : + 1 byte type/flag. 597 : : + 4 bytes formatID 598 : : + 1 byte gtrid length 599 : : + 1 byte bqual length 600 : : + 128 bytes (max) gtrid and bqual strings. 601 : : +*/ 602 : : +struct chunk_data_xa_complete : 603 : : + public chunk_data_from_buf<1 + 4 + 1 + 1 + 128> { 604 : : + 605 : 96 : + chunk_data_xa_complete(const XID *xid, bool is_commit) 606 : 96 : + { 607 : 96 : + buffer[0]= (is_commit ? IBB_FL_XA_TYPE_COMMIT : IBB_FL_XA_TYPE_ROLLBACK); 608 : 96 : + int4store(&buffer[1], xid->formatID); 609 : 96 : + ut_a(xid->gtrid_length >= 0 && xid->gtrid_length <= 64); 610 : 96 : + buffer[5]= (uchar)xid->gtrid_length; 611 : 96 : + ut_a(xid->bqual_length >= 0 && xid->bqual_length <= 64); 612 : 96 : + buffer[6]= (uchar)xid->bqual_length; 613 : 96 : + memcpy(&buffer[7], &xid->data[0], xid->gtrid_length + xid->bqual_length); 614 : 96 : + data_remain= 615 : 96 : + static_cast(7 + xid->gtrid_length + xid->bqual_length); 616 : 96 : + } 617 : 96 : + ~chunk_data_xa_complete() { } 618 : : +}; 619 : : + 620 : : + 621 : : +class gtid_search { 622 : : +public: 623 : : + gtid_search(); 624 : : + ~gtid_search(); 625 : : + int find_gtid_pos(slave_connection_state *pos, 626 : : + rpl_binlog_state_base *out_state, uint64_t *out_file_no, 627 : : + uint64_t *out_offset); 628 : : +private: 629 : : + uint64_t cur_open_file_no; 630 : : + uint64_t cur_open_file_length; 631 : : + File cur_open_file; 632 : : +}; 633 : : + 634 : : + 635 : : +struct found_binlogs { 636 : : + uint64_t last_file_no, prev_file_no, earliest_file_no; 637 : : + size_t last_size, prev_size, total_size; 638 : : + int num_found; 639 : : + /* Default constructor to silence compiler warnings -Wuninitialized. */ 640 : : + found_binlogs()= default; 641 : : +}; 642 : : + 643 : : + 644 : : +/** 645 : : + Class used during startup to recover any pending prepared XID for internal 646 : : + 2pc or user XA. Filled-in from prepare and commit/rollback records found 647 : : + during scan of the binlog file, and used by the server to decide whether 648 : : + to keep, commit, or roll back any prepared transactions/XID found in engines. 649 : : +*/ 650 : : +class ibb_binlog_xid_info : public handler_binlog_xid_info 651 : : +{ 652 : : +public: 653 : : + /* 654 : : + In addition to the information needed by the server layer, we need 655 : : + references to the binlogged OOB data of prepared transactions to 656 : : + populate entries in our internal ibb_xa_xid_hash. 657 : : + */ 658 : : + uint64_t num_oob_nodes; 659 : : + uint64_t first_oob_file_no; 660 : : + uint64_t first_oob_offset; 661 : : + uint64_t last_oob_file_no; 662 : : + uint64_t last_oob_offset; 663 : : + /* This is the file_no of the prepare/commit/rollback record itself. */ 664 : : + uint64_t rec_file_no; 665 : : + 666 : 92 : + ibb_binlog_xid_info(binlog_xid_state typ, uint64_t rec_file_no_) : 667 : 92 : + handler_binlog_xid_info(typ), rec_file_no(rec_file_no_) { } 668 : 184 : + virtual ~ibb_binlog_xid_info() override { }; 669 : : +}; 670 : : + 671 : : + 672 : : +/** 673 : : + This structure holds the state needed during InnoDB recovery for recovering 674 : : + binlog tablespace files. 675 : : +*/ 676 : : +class binlog_recovery { 677 : : +public: 678 : : + struct found_binlogs scan_result; 679 : : + byte *page_buf; 680 : : + const char *binlog_dir; 681 : : + /* 682 : : + The current file number being recovered. 683 : : + This starts out as the most recent existing non-empty binlog that has a 684 : : + starting LSN no bigger than the recovery starting LSN. This should always be 685 : : + one of the two most recent binlog files found at startup. 686 : : + */ 687 : : + uint64_t cur_file_no; 688 : : + /* The physical length of cur_file_no file. */ 689 : : + uint64_t cur_phys_size; 690 : : + /* 691 : : + The starting LSN (as stored in the header of the binlog tablespace file). 692 : : + No redo prior to this LSN should be applied to this file. 693 : : + */ 694 : : + lsn_t start_file_lsn; 695 : : + /* 696 : : + The LSN of the previously applied redo record. Used to ignore duplicate 697 : : + redo records passed from the InnoDB recovery layer, eg. in multi-batch 698 : : + recovery. Also prev_size, prev_page_no, prev_offset, prev_space_id. 699 : : + */ 700 : : + lsn_t prev_lsn; 701 : : + size_t prev_size; 702 : : + 703 : : + /* Open file for cur_file_no, or -1 if not open. */ 704 : : + File cur_file_fh; 705 : : + /* The sofar position of redo in cur_file_no (end point of previous redo). */ 706 : : + uint32_t cur_page_no; 707 : : + uint32_t cur_page_offset; 708 : : + 709 : : + uint32_t prev_page_no; 710 : : + uint16_t prev_offset; 711 : : + bool prev_space_id; 712 : : + 713 : : + /* The path to cur_file_no. */ 714 : : + char full_path[OS_FILE_MAX_PATH]; 715 : : + 716 : : + bool inited; 717 : : + /* 718 : : + Flag set in case of severe error and --innodb-force_recovery to completely 719 : : + skip any binlog recovery. 720 : : + */ 721 : : + bool skip_recovery; 722 : : + /* 723 : : + Special case, if we start from completely empty (no non-empty binlog files). 724 : : + This should recover into an empty binlog state. 725 : : + */ 726 : : + bool start_empty; 727 : : + /* 728 : : + Special case: The last two files are empty. Then we ignore the last empty 729 : : + file and use the 2 previous files instead. The ignored file is deleted only 730 : : + after successful recovery, to try to avoid destroying data in case of 731 : : + recovery problems. 732 : : + */ 733 : : + bool ignore_last; 734 : : + /* 735 : : + Mark the case where the first binlog tablespace file we need to consider for 736 : : + recovery has file LSN that is later than the first redo record; in this case 737 : : + we need to skip records until the first one that applies to this file. 738 : : + */ 739 : : + bool skipping_early_lsn; 740 : : + /* 741 : : + Skip any initial records until the start of a page. We are guaranteed that 742 : : + any page that needs to be recovered will have recovery data for the whole 743 : : + page, and this way we never need to read-modify-write pages during recovery. 744 : : + */ 745 : : + bool skipping_partial_page; 746 : : + 747 : : + bool init_recovery(bool space_id, uint32_t page_no, uint16_t offset, 748 : : + lsn_t start_lsn, lsn_t lsn, 749 : : + const byte *buf, size_t size) noexcept; 750 : : + bool apply_redo(bool space_id, uint32_t page_no, uint16_t offset, 751 : : + lsn_t start_lsn, lsn_t lsn, 752 : : + const byte *buf, size_t size) noexcept; 753 : : + int get_header(uint64_t file_no, lsn_t &out_lsn, bool &out_empty) noexcept; 754 : : + bool init_recovery_from(uint64_t file_no, lsn_t file_lsn, uint32_t page_no, 755 : : + uint16_t offset, lsn_t lsn, 756 : : + const byte *buf, size_t size) noexcept; 757 : : + void init_recovery_empty() noexcept; 758 : : + void init_recovery_skip_all() noexcept; 759 : : + void end_actions(bool recovery_successful) noexcept; 760 : : + void release() noexcept; 761 : : + bool open_cur_file() noexcept; 762 : : + bool flush_page() noexcept; 763 : : + void zero_out_cur_file(); 764 : : + bool close_file() noexcept; 765 : : + bool next_file() noexcept; 766 : : + bool next_page() noexcept; 767 : : + bool update_page_from_record(uint16_t offset, 768 : : + const byte *buf, size_t size) noexcept; 769 : : +}; 770 : : + 771 : : + 772 : : +static binlog_recovery recover_obj; 773 : : + 774 : : + 775 : : +static void innodb_binlog_prealloc_thread(); 776 : : +static int scan_for_binlogs(const char *binlog_dir, found_binlogs *binlog_files, 777 : : + bool error_if_missing) noexcept; 778 : : +static int innodb_binlog_discover(); 779 : : +static bool binlog_state_recover(uint64_t *out_xa_file_no, 780 : : + uint64_t *out_xa_offset); 781 : : +static void innodb_binlog_autopurge(uint64_t first_open_file_no, LF_PINS *pins); 782 : : +static bool binlog_scan_for_xid(uint64_t start_file_no, uint64_t start_offset, 783 : : + HASH *hash); 784 : : +static bool ibb_init_xid_hash(HASH *hash, LF_PINS *pins); 785 : : + 786 : : +/** 787 : : + Read the header of a binlog tablespace file identified by file_no. 788 : : + Sets the out_empty false if the file is empty or has checksum error (or 789 : : + is missing). 790 : : + Else sets out_empty true and sets out_lsn from the header. 791 : : + 792 : : + Returns: 793 : : + -1 error 794 : : + 0 File is missing (ENOENT) or has bad checksum on first page. 795 : : + 1 File found (but may be empty according to out_empty). 796 : : +*/ 797 : : +int 798 : 113 : +get_binlog_header(const char *binlog_path, byte *page_buf, 799 : : + lsn_t &out_lsn, bool &out_empty) noexcept 800 : : +{ 801 : : + binlog_header_data header; 802 : : + 803 : 113 : + out_empty= true; 804 : 113 : + out_lsn= 0; 805 : : + 806 : 113 : + File fh= my_open(binlog_path, O_RDONLY | O_BINARY, MYF(0)); 807 : 113 : + if (fh < (File)0) 808 : 2 : + return (my_errno == ENOENT ? 0 : -1); 809 : 111 : + size_t read= my_pread(fh, page_buf, ibb_page_size, 0, MYF(0)); 810 : 111 : + my_close(fh, MYF(0)); 811 : 111 : + if (UNIV_UNLIKELY(read == (size_t)-1)) 812 : 0 : + return -1; 813 : 111 : + if (read == 0) 814 : 0 : + return 0; 815 : : + /* 816 : : + If the crc32 does not match, the page was not written properly, so treat 817 : : + it as an empty file. 818 : : + */ 819 : 111 : + const uint32_t payload= (uint32_t)ibb_page_size - BINLOG_PAGE_CHECKSUM; 820 : 111 : + uint32_t crc32= uint4korr(page_buf + payload); 821 : 111 : + if (UNIV_UNLIKELY(crc32 != my_crc32c(0, page_buf, payload))) 822 : 29 : + return 0; 823 : : + 824 : 82 : + fsp_binlog_extract_header_page(page_buf, &header); 825 : 82 : + if (header.is_invalid) 826 : 0 : + return 0; 827 : 82 : + if (!header.is_empty) 828 : : + { 829 : 82 : + out_empty= false; 830 : 82 : + out_lsn= header.start_lsn; 831 : : + } 832 : 82 : + return 1; 833 : : +} 834 : : + 835 : : + 836 : : +int 837 : 55 : +binlog_recovery::get_header(uint64_t file_no, lsn_t &out_lsn, bool &out_empty) 838 : : + noexcept 839 : : +{ 840 : : + char full_path[OS_FILE_MAX_PATH]; 841 : 55 : + binlog_name_make(full_path, file_no, binlog_dir); 842 : 55 : + return get_binlog_header(full_path, page_buf, out_lsn, out_empty); 843 : : +} 844 : : + 845 : : + 846 : 29 : +bool binlog_recovery::init_recovery(bool space_id, uint32_t page_no, 847 : : + uint16_t offset, 848 : : + lsn_t start_lsn, lsn_t end_lsn, 849 : : + const byte *buf, size_t size) noexcept 850 : : +{ 851 : : + /* Start by initializing resource pointers so we are safe to releaes(). */ 852 : 29 : + cur_file_fh= (File)-1; 853 : 29 : + if (!(page_buf= static_cast 854 : 58 : + (ut_malloc(ibb_page_size, mem_key_binlog)))) 855 : : + { 856 : 0 : + my_error(ER_OUTOFMEMORY, MYF(MY_WME), ibb_page_size); 857 : 0 : + return true; 858 : : + } 859 : 29 : + memset(page_buf, 0, ibb_page_size); 860 : 29 : + inited= true; 861 : : + /* 862 : : + ToDo: It would be good to find a way to not duplicate this logic for 863 : : + where the binlog tablespace filess are stored with the code in 864 : : + innodb_binlog_init(). But it's a bit awkward, because InnoDB recovery 865 : : + runs during plugin init, so not even available for the server to call 866 : : + into until after recovery is done. 867 : : + */ 868 : 29 : + binlog_dir= opt_binlog_directory; 869 : 29 : + if (!binlog_dir || !binlog_dir[0]) 870 : 15 : + binlog_dir= "."; 871 : 29 : + if (scan_for_binlogs(binlog_dir, &scan_result, true) <= 0) 872 : 0 : + return true; 873 : : + 874 : : + /* 875 : : + Here we find the two most recent, non-empty binlogs to do recovery on. 876 : : + Before we allocate binlog tablespace file N+2, we flush and fsync file N 877 : : + to disk. This ensures that we only ever need to apply redo records to the 878 : : + two most recent files during recovery. 879 : : + 880 : : + A special case however arises if the two most recent binlog files are 881 : : + both completely empty. Then we do not have any LSN to match against to 882 : : + know if a redo record applies to one of these two files, or to an earlier 883 : : + file with same value of bit 0 of the file_no. In this case, we ignore the 884 : : + most recent file (deleting it later after successful recovery), and 885 : : + consider instead the two prior files, the first of which is guaranteed to 886 : : + have durably saved a starting LSN to use. 887 : : + 888 : : + Hence the loop, which can only ever have one or two iterations. 889 : : + 890 : : + A further special case is if there are fewer than two (or three if last 891 : : + two are empty) files. If there are no files, or only empty files, then the 892 : : + server must have stopped just after RESET MASTER (or just after 893 : : + initializing the binlogs at first startup), and we should just start the 894 : : + binlogs from scratch. 895 : : + */ 896 : 29 : + ignore_last= false; 897 : 29 : + uint64_t file_no2= scan_result.last_file_no; 898 : 29 : + uint64_t file_no1= scan_result.prev_file_no; 899 : 29 : + int num_binlogs= scan_result.num_found; 900 : : + for (;;) 901 : : + { 902 : 30 : + lsn_t lsn1= 0, lsn2= 0; 903 : 30 : + bool is_empty1= true, is_empty2= true; 904 : 30 : + int res2= get_header(file_no2, lsn2, is_empty2); 905 : : + 906 : 30 : + if (num_binlogs == 0 || 907 : 3 : + (num_binlogs == 1 && is_empty2)) 908 : : + { 909 : 2 : + init_recovery_empty(); 910 : 29 : + return false; 911 : : + } 912 : 28 : + if (num_binlogs == 1) 913 : : + { 914 : 3 : + uint64_t start_file_no= file_no2; 915 : : + /* 916 : : + Only one binlog file found. 917 : : + 918 : : + This first recovery record may apply to the previous file (which has 919 : : + then presumably been purged since the last checkpoint). Or it may 920 : : + apply to this file, or only to the following file. The case where it 921 : : + is not this file needs a bit of care. 922 : : + 923 : : + If the recovery record lsn is less than the lsn in this file, we know 924 : : + that it must apply to the previous file, and we can start from this 925 : : + file. 926 : : + 927 : : + If the recovery record lsn is equal or greater, then it can apply to 928 : : + the previous file if it is part of a mini-transaction that spans into 929 : : + this file. Or it can apply to the following file. If it applies to the 930 : : + following file it must have page_no=0 and offset=0, since that file is 931 : : + missing and will be recovered from scratch. Conversely, if the record 932 : : + has page_no=0 and offset=0, it cannot apply to the previous file, as 933 : : + we keep mini-transactions smaller than one binlog file. 934 : : + */ 935 : 3 : + if (space_id != (file_no2 & 1) && start_lsn >= lsn2 && 936 : 0 : + page_no == 0 && offset == 0) 937 : 0 : + ++start_file_no; 938 : 3 : + return init_recovery_from(start_file_no, lsn2, page_no, offset, 939 : 3 : + start_lsn, buf, size); 940 : : + } 941 : : + 942 : 25 : + int res1= get_header(file_no1, lsn1, is_empty1); 943 : : + 944 : 25 : + if (res2 < 0 && !srv_force_recovery) 945 : : + { 946 : 0 : + sql_print_error("InnoDB: I/O error reading binlog file number %" PRIu64, 947 : : + file_no2); 948 : 0 : + return true; 949 : : + } 950 : 25 : + if (res1 < 0 && !srv_force_recovery) 951 : : + { 952 : 0 : + sql_print_error("InnoDB: I/O error reading binlog file number %" PRIu64, 953 : : + file_no1); 954 : 0 : + return true; 955 : : + } 956 : 25 : + if (is_empty1 && is_empty2) 957 : : + { 958 : 1 : + if (!ignore_last) 959 : : + { 960 : 1 : + ignore_last= true; 961 : 1 : + if (file_no2 > scan_result.earliest_file_no) 962 : : + { 963 : 1 : + --file_no2; 964 : 1 : + if (file_no1 > scan_result.earliest_file_no) 965 : 1 : + --file_no1; 966 : : + else 967 : 0 : + --num_binlogs; 968 : : + } 969 : : + else 970 : 0 : + --num_binlogs; 971 : 1 : + continue; 972 : : + } 973 : 0 : + if (srv_force_recovery) 974 : : + { 975 : : + /* 976 : : + If the last 3 files are empty, we cannot get an LSN to know which 977 : : + records apply to each file. This should not happen unless there is 978 : : + damage to the file system. If force recovery is requested, we must 979 : : + simply do no recovery at all on the binlog files. 980 : : + */ 981 : 0 : + sql_print_warning("InnoDB: Binlog tablespace file recovery is not " 982 : : + "possible. Recovery is skipped due to " 983 : : + "--innodb-force-recovery"); 984 : 0 : + init_recovery_skip_all(); 985 : 0 : + return false; 986 : : + } 987 : 0 : + sql_print_error("InnoDB: Last 3 binlog tablespace files are all empty. " 988 : : + "Recovery is not possible"); 989 : 0 : + return true; 990 : : + } 991 : 24 : + if (is_empty2) 992 : : + { 993 : : + /* 994 : : + As above for the case where only one file is found, we need to 995 : : + carefully distinguish the case where the recovery record applies to 996 : : + file_no1-1 or file_no1+1; when start_lsn >= lsn1, the record can 997 : : + apply to file_no1+1 only if it is for page_no==0 and offset==0. 998 : : + */ 999 : 15 : + if (space_id != (file_no1 & 1) && start_lsn >= lsn1 && 1000 : 0 : + page_no == 0 && offset == 0) 1001 : 0 : + return init_recovery_from(file_no2, lsn1, page_no, offset, 1002 : 0 : + start_lsn, buf, size); 1003 : : + else 1004 : 15 : + return init_recovery_from(file_no1, lsn1, page_no, offset, 1005 : 15 : + start_lsn, buf, size); 1006 : : + } 1007 : 9 : + else if (space_id == (file_no2 & 1) && start_lsn >= lsn2) 1008 : : + { 1009 : : + /* The record must apply to file_no2. */ 1010 : 0 : + return init_recovery_from(file_no2, lsn2, 1011 : 0 : + page_no, offset, start_lsn, buf, size); 1012 : : + } 1013 : : + else 1014 : : + { 1015 : : + /* 1016 : : + The record cannot apply to file_no2, as either the space_id differs 1017 : : + or the lsn is too early. Start from file_no1. 1018 : : + */ 1019 : 9 : + return init_recovery_from(file_no1, lsn1, 1020 : 9 : + page_no, offset, start_lsn, buf, size); 1021 : : + } 1022 : : + /* NotReached. */ 1023 : 1 : + } 1024 : : +} 1025 : : + 1026 : : + 1027 : : +bool 1028 : 27 : +binlog_recovery::init_recovery_from(uint64_t file_no, lsn_t file_lsn, 1029 : : + uint32_t page_no, uint16_t offset, 1030 : : + lsn_t lsn, const byte *buf, size_t size) 1031 : : + noexcept 1032 : : +{ 1033 : 27 : + cur_file_no= file_no; 1034 : 27 : + cur_phys_size= 0; 1035 : 27 : + start_file_lsn= file_lsn; 1036 : 27 : + prev_lsn= lsn; 1037 : 27 : + prev_space_id= file_no & 1; 1038 : 27 : + prev_page_no= page_no; 1039 : 27 : + prev_offset= offset; 1040 : 27 : + prev_size= size; 1041 : 27 : + cur_page_no= page_no; 1042 : 27 : + cur_page_offset= 0; 1043 : 27 : + skip_recovery= false; 1044 : 27 : + start_empty= false; 1045 : 27 : + skipping_partial_page= true; 1046 : 27 : + if (lsn < start_file_lsn) 1047 : 26 : + skipping_early_lsn= true; 1048 : : + else 1049 : : + { 1050 : 1 : + skipping_early_lsn= false; 1051 : 1 : + if (offset <= BINLOG_PAGE_DATA) 1052 : : + { 1053 : 1 : + skipping_partial_page= false; 1054 : 1 : + return update_page_from_record(offset, buf, size); 1055 : : + } 1056 : : + } 1057 : 26 : + return false; 1058 : : +} 1059 : : + 1060 : : + 1061 : : +/** 1062 : : + Initialize recovery from the state where there are no binlog files, or only 1063 : : + completely empty binlog files. In this case we have no file LSN to compare 1064 : : + redo records against. 1065 : : + 1066 : : + This can only happen if we crash immediately after RESET MASTER (or fresh 1067 : : + server installation) as an initial file header is durably written to disk 1068 : : + before binlogging new data. Therefore we should skip _all_ redo records and 1069 : : + recover into a completely empty state. 1070 : : +*/ 1071 : : +void 1072 : 2 : +binlog_recovery::init_recovery_empty() noexcept 1073 : : +{ 1074 : 2 : + cur_file_no= 0; 1075 : 2 : + cur_phys_size= 0; 1076 : 2 : + start_file_lsn= (lsn_t)0; 1077 : 2 : + prev_lsn= (lsn_t)0; 1078 : 2 : + prev_space_id= 0; 1079 : 2 : + prev_page_no= 0; 1080 : 2 : + prev_offset= 0; 1081 : 2 : + prev_size= 0; 1082 : 2 : + cur_page_no= 0; 1083 : 2 : + cur_page_offset= 0; 1084 : 2 : + skip_recovery= false; 1085 : 2 : + start_empty= true; 1086 : 2 : + ignore_last= false; 1087 : 2 : + skipping_early_lsn= false; 1088 : 2 : + skipping_partial_page= true; 1089 : 2 : +} 1090 : : + 1091 : : + 1092 : : +void 1093 : 0 : +binlog_recovery::init_recovery_skip_all() noexcept 1094 : : +{ 1095 : 0 : + skip_recovery= true; 1096 : 0 : +} 1097 : : + 1098 : : + 1099 : : +void 1100 : 29 : +binlog_recovery::end_actions(bool recovery_successful) noexcept 1101 : : +{ 1102 : : + char full_path[OS_FILE_MAX_PATH]; 1103 : 29 : + if (recovery_successful && !skip_recovery) 1104 : : + { 1105 : 29 : + if (!start_empty) 1106 : : + { 1107 : 27 : + if (cur_page_offset) 1108 : 25 : + flush_page(); 1109 : 27 : + if (cur_file_fh > (File)-1) 1110 : 25 : + zero_out_cur_file(); 1111 : 27 : + close_file(); 1112 : 27 : + ++cur_file_no; 1113 : : + } 1114 : : + 1115 : : + /* 1116 : : + Delete any binlog tablespace files following the last recovered file. 1117 : : + These files could be pre-allocated but never used files, or they could be 1118 : : + files that were written with data that was eventually not recovered due 1119 : : + to --innodb-flush-log-at-trx-commit=0|2. 1120 : : + */ 1121 : 44 : + for (uint64_t i= cur_file_no; 1122 : 44 : + scan_result.num_found >= 1 && i <= scan_result.last_file_no; 1123 : : + ++i) 1124 : : + { 1125 : 15 : + binlog_name_make(full_path, i, binlog_dir); 1126 : 15 : + if (my_delete(full_path, MYF(MY_WME))) 1127 : 0 : + sql_print_warning("InnoDB: Could not delete empty file '%s' (" 1128 : 0 : + "error: %d)", full_path, my_errno); 1129 : : + } 1130 : : + } 1131 : 29 : + release(); 1132 : 29 : +} 1133 : : + 1134 : : + 1135 : : +void 1136 : 29 : +binlog_recovery::release() noexcept 1137 : : +{ 1138 : 29 : + if (cur_file_fh >= (File)0) 1139 : : + { 1140 : 0 : + my_close(cur_file_fh, MYF(0)); 1141 : 0 : + cur_file_fh= (File)-1; 1142 : : + } 1143 : 29 : + ut_free(page_buf); 1144 : 29 : + page_buf= nullptr; 1145 : 29 : + inited= false; 1146 : 29 : +} 1147 : : + 1148 : : + 1149 : : +bool 1150 : 35 : +binlog_recovery::open_cur_file() noexcept 1151 : : +{ 1152 : 35 : + if (cur_file_fh >= (File)0) 1153 : 0 : + my_close(cur_file_fh, MYF(0)); 1154 : 35 : + binlog_name_make(full_path, cur_file_no, binlog_dir); 1155 : 35 : + cur_file_fh= my_open(full_path, O_RDWR | O_BINARY, MYF(0)); 1156 : 35 : + if (cur_file_fh < (File)0) 1157 : : + { 1158 : : + /* 1159 : : + If we are on page 0 and the binlog file does not exist, then we should 1160 : : + create it (and recover its content). 1161 : : + Otherwise, it is an error, we cannot recover it as we are missing the 1162 : : + start of it. 1163 : : + */ 1164 : 0 : + if (my_errno != ENOENT || 1165 : 0 : + cur_page_no != 0 || 1166 : 0 : + (cur_file_fh= my_open(full_path, O_RDWR | O_CREAT | O_TRUNC | 1167 : : + O_BINARY, MYF(0))) < (File)0) 1168 : : + { 1169 : 0 : + my_error(EE_FILENOTFOUND, MYF(MY_WME), full_path, my_errno); 1170 : 0 : + return true; 1171 : : + } 1172 : : + } 1173 : 35 : + cur_phys_size= (uint64_t)my_seek(cur_file_fh, 0, MY_SEEK_END, MYF(0)); 1174 : 35 : + return false; 1175 : : +} 1176 : : + 1177 : : + 1178 : : +bool 1179 : 212 : +binlog_recovery::flush_page() noexcept 1180 : : +{ 1181 : 247 : + if (cur_file_fh < (File)0 && 1182 : 35 : + open_cur_file()) 1183 : 0 : + return true; 1184 : : + size_t res= 1185 : 212 : + crc32_pwrite_page(cur_file_fh, page_buf, cur_page_no, MYF(MY_WME)); 1186 : 212 : + if (res != ibb_page_size) 1187 : 0 : + return true; 1188 : 212 : + cur_page_offset= 0; 1189 : 212 : + memset(page_buf, 0, ibb_page_size); 1190 : 212 : + return false; 1191 : : +} 1192 : : + 1193 : : + 1194 : : +void 1195 : 25 : +binlog_recovery::zero_out_cur_file() 1196 : : +{ 1197 : 25 : + if (cur_file_fh < (File)0) 1198 : 0 : + return; 1199 : : + 1200 : : + /* Recover the original size from the current file. */ 1201 : 25 : + int res= crc32_pread_page(cur_file_fh, page_buf, 0, MYF(0)); 1202 : 25 : + if (res <= 0) 1203 : : + { 1204 : 0 : + sql_print_warning("InnoDB: Could not read last binlog file during recovery"); 1205 : 0 : + return; 1206 : : + } 1207 : : + binlog_header_data header; 1208 : 25 : + fsp_binlog_extract_header_page(page_buf, &header); 1209 : : + 1210 : 25 : + if (header.is_invalid) 1211 : : + { 1212 : 0 : + sql_print_warning("InnoDB: Invalid header page in last binlog file " 1213 : : + "during recovery"); 1214 : 0 : + return; 1215 : : + } 1216 : 25 : + if (header.is_empty) 1217 : : + { 1218 : 0 : + sql_print_warning("InnoDB: Empty binlog file header found during recovery"); 1219 : 0 : + ut_ad(0); 1220 : 0 : + return; 1221 : : + } 1222 : : + 1223 : : + /* Fill up or truncate the file to its original size. */ 1224 : 25 : + if (my_chsize(cur_file_fh, (my_off_t)header.page_count << ibb_page_size_shift, 1225 : : + 0, MYF(0))) 1226 : 0 : + sql_print_warning("InnoDB: Could not change the size of last binlog file " 1227 : 0 : + "during recovery (error: %d)", my_errno); 1228 : 258 : + for (uint32_t i= cur_page_no + 1; i < header.page_count; ++i) 1229 : : + { 1230 : 466 : + if (my_pread(cur_file_fh, page_buf, ibb_page_size, 1231 : 233 : + (my_off_t)i << ibb_page_size_shift, MYF(0)) < 1232 : : + (size_t)ibb_page_size) 1233 : 0 : + break; 1234 : : + /* Check if page already zeroed out. */ 1235 : 233 : + if (page_buf[0] == 0 && !memcmp(page_buf, page_buf+1, ibb_page_size - 1)) 1236 : 232 : + continue; 1237 : 1 : + memset(page_buf, 0, ibb_page_size); 1238 : 2 : + if (my_pwrite(cur_file_fh, page_buf, ibb_page_size, 1239 : 1 : + (uint64_t)i << ibb_page_size_shift, MYF(MY_WME)) < 1240 : : + (size_t)ibb_page_size) 1241 : : + { 1242 : 0 : + sql_print_warning("InnoDB: Error writing to last binlog file during " 1243 : 0 : + "recovery (error code: %d)", my_errno); 1244 : 0 : + break; 1245 : : + } 1246 : : + } 1247 : : +} 1248 : : + 1249 : : + 1250 : : +bool 1251 : 37 : +binlog_recovery::close_file() noexcept 1252 : : +{ 1253 : 37 : + if (cur_file_fh >= (File)0) 1254 : : + { 1255 : 35 : + if (my_sync(cur_file_fh, MYF(MY_WME))) 1256 : 0 : + return true; 1257 : 35 : + my_close(cur_file_fh, (File)0); 1258 : 35 : + cur_file_fh= (File)-1; 1259 : 35 : + cur_phys_size= 0; 1260 : : + } 1261 : 37 : + return false; 1262 : : +} 1263 : : + 1264 : : + 1265 : : +bool 1266 : 10 : +binlog_recovery::next_file() noexcept 1267 : : +{ 1268 : 10 : + if (cur_page_offset && flush_page()) 1269 : 0 : + return true; 1270 : 10 : + if (close_file()) 1271 : 0 : + return true; 1272 : 10 : + ++cur_file_no; 1273 : 10 : + cur_page_no= 0; 1274 : 10 : + return false; 1275 : : +} 1276 : : + 1277 : : + 1278 : : +bool 1279 : 177 : +binlog_recovery::next_page() noexcept 1280 : : +{ 1281 : 177 : + if (cur_page_offset && flush_page()) 1282 : 0 : + return true; 1283 : 177 : + ++cur_page_no; 1284 : 177 : + return false; 1285 : : +} 1286 : : + 1287 : : + 1288 : : +bool 1289 : 31239 : +binlog_recovery::apply_redo(bool space_id, uint32_t page_no, uint16_t offset, 1290 : : + lsn_t start_lsn, lsn_t end_lsn, 1291 : : + const byte *buf, size_t size) noexcept 1292 : : +{ 1293 : 31239 : + if (UNIV_UNLIKELY(skip_recovery) || start_empty) 1294 : 7680 : + return false; 1295 : : + 1296 : : + /* 1297 : : + In a multi-batch recovery, InnoDB recovery redo parser will sometimes 1298 : : + pass the same record(s) twice to the binlog recovery. 1299 : : + 1300 : : + The binlog recovery code wants to do consistency checks that records are 1301 : : + processed in strict order, so we handle this special case by detecting 1302 : : + and ignoring duplicate records. 1303 : : + 1304 : : + A duplicate record is determined by being in the same mtr (identified by 1305 : : + end_lsn); and having page_no/offset either earlier in the same space_id, 1306 : : + or later in a different space_id. Using the property that an mtr is always 1307 : : + smaller than the binlog maximum file size. 1308 : : + */ 1309 : 23559 : + if (end_lsn == prev_lsn && 1310 : 796 : + ( ( space_id == prev_space_id && 1311 : 757 : + ( ((uint64_t)page_no << 32 | offset) <= 1312 : 757 : + ((uint64_t)prev_page_no << 32 | prev_offset) ) ) || 1313 : 796 : + ( space_id != prev_space_id && 1314 : 39 : + ( ((uint64_t)page_no << 32 | offset) > 1315 : 39 : + ((uint64_t)prev_page_no << 32 | prev_offset) ) ) ) ) 1316 : 0 : + return false; 1317 : 23559 : + prev_lsn= end_lsn; 1318 : 23559 : + prev_space_id= space_id; 1319 : 23559 : + prev_page_no= page_no; 1320 : 23559 : + prev_offset= offset; 1321 : 23559 : + prev_size= size; 1322 : : + 1323 : 23559 : + if (skipping_partial_page) 1324 : : + { 1325 : 93 : + if (offset > BINLOG_PAGE_DATA) 1326 : 67 : + return false; 1327 : 26 : + skipping_partial_page= false; 1328 : : + } 1329 : : + 1330 : 23492 : + if (skipping_early_lsn) 1331 : : + { 1332 : 17327 : + if (start_lsn < start_file_lsn || space_id != (cur_file_no & 1)) 1333 : 17303 : + return false; /* Skip record for earlier file that's already durable. */ 1334 : : + /* Now reset the current page to match the real starting point. */ 1335 : 24 : + cur_page_no= page_no; 1336 : : + } 1337 : : + 1338 : 6189 : + if (UNIV_UNLIKELY(start_lsn < start_file_lsn)) 1339 : : + { 1340 : 0 : + ut_a(!skipping_early_lsn /* Was handled in condition above */); 1341 : 0 : + if (!srv_force_recovery) 1342 : : + { 1343 : 0 : + sql_print_error("InnoDB: Unexpected LSN " LSN_PF " during recovery, " 1344 : : + "expected at least " LSN_PF, start_lsn, start_file_lsn); 1345 : 0 : + return true; 1346 : : + } 1347 : 0 : + sql_print_warning("InnoDB: Ignoring unexpected LSN " LSN_PF " during " 1348 : : + "recovery, ", start_lsn); 1349 : 0 : + return false; 1350 : : + } 1351 : 6189 : + skipping_early_lsn= false; 1352 : : + 1353 : : + /* Test for moving to the next file. */ 1354 : 6189 : + if (space_id != (cur_file_no & 1)) 1355 : : + { 1356 : : + /* Check that we recovered all of this file. */ 1357 : 10 : + if ( ( (cur_page_offset > BINLOG_PAGE_DATA && 1358 : 10 : + cur_page_offset < ibb_page_size - BINLOG_PAGE_DATA_END) || 1359 : 10 : + cur_page_no + (cur_page_offset > BINLOG_PAGE_DATA) < 1360 : 10 : + cur_phys_size >> ibb_page_size_shift) && 1361 : 0 : + !srv_force_recovery) 1362 : : + { 1363 : 0 : + sql_print_error("InnoDB: Missing recovery record at end of file_no=%" 1364 : : + PRIu64 ", LSN " LSN_PF, cur_file_no, start_lsn); 1365 : 0 : + return true; 1366 : : + } 1367 : : + 1368 : : + /* Check that we recover from the start of the next file. */ 1369 : 10 : + if ((page_no > 0 || offset > BINLOG_PAGE_DATA) && !srv_force_recovery) 1370 : : + { 1371 : 0 : + sql_print_error("InnoDB: Missing recovery record at start of file_no=%" 1372 : 0 : + PRIu64 ", LSN " LSN_PF, cur_file_no+1, start_lsn); 1373 : 0 : + return true; 1374 : : + } 1375 : : + 1376 : 10 : + if (next_file()) 1377 : 0 : + return true; 1378 : : + } 1379 : : + /* Test for moving to the next page. */ 1380 : 6179 : + else if (page_no != cur_page_no) 1381 : : + { 1382 : 177 : + if (cur_page_offset > BINLOG_PAGE_DATA && 1383 : 177 : + cur_page_offset < ibb_page_size - BINLOG_PAGE_DATA_END && 1384 : 0 : + !srv_force_recovery) 1385 : : + { 1386 : 0 : + sql_print_error("InnoDB: Missing recovery record in file_no=%" 1387 : : + PRIu64 ", page_no=%u, LSN " LSN_PF, 1388 : : + cur_file_no, cur_page_no, start_lsn); 1389 : 0 : + return true; 1390 : : + } 1391 : : + 1392 : 177 : + if ((page_no != cur_page_no + 1 || offset > BINLOG_PAGE_DATA) && 1393 : 0 : + !srv_force_recovery) 1394 : : + { 1395 : 0 : + sql_print_error("InnoDB: Missing recovery record in file_no=%" 1396 : : + PRIu64 ", page_no=%u, LSN " LSN_PF, 1397 : 0 : + cur_file_no, cur_page_no + 1, start_lsn); 1398 : 0 : + return true; 1399 : : + } 1400 : : + 1401 : 177 : + if (next_page()) 1402 : 0 : + return true; 1403 : : + } 1404 : : + /* Test no gaps in offset. */ 1405 : 6002 : + else if (offset != cur_page_offset && 1406 : 0 : + offset > BINLOG_PAGE_DATA && 1407 : 0 : + !srv_force_recovery) 1408 : : + { 1409 : 0 : + sql_print_error("InnoDB: Missing recovery record in file_no=%" 1410 : : + PRIu64 ", page_no=%u, LSN " LSN_PF, 1411 : : + cur_file_no, cur_page_no, start_lsn); 1412 : 0 : + return true; 1413 : : + } 1414 : : + 1415 : 6189 : + if (offset + size >= ibb_page_size) 1416 : 0 : + return !srv_force_recovery; 1417 : : + 1418 : 6189 : + return update_page_from_record(offset, buf, size); 1419 : : +} 1420 : : + 1421 : : + 1422 : : +bool 1423 : 6190 : +binlog_recovery::update_page_from_record(uint16_t offset, 1424 : : + const byte *buf, size_t size) noexcept 1425 : : +{ 1426 : 6190 : + memcpy(page_buf + offset, buf, size); 1427 : 6190 : + if (cur_page_no == 0 && offset == 0) 1428 : : + { 1429 : : + binlog_header_data header; 1430 : : + /* 1431 : : + This recovery record is for the file header page. 1432 : : + This record is special, it covers only the used part of the header page. 1433 : : + The reaminder of the page must be set to zeroes. 1434 : : + Additionally, there is an extra CRC corresponding to a minimum 1435 : : + page size of IBB_PAGE_SIZE_MIN, in anticipation for future configurable 1436 : : + page size. 1437 : : + */ 1438 : 34 : + memset(page_buf + size, 0, ibb_page_size - (size + BINLOG_PAGE_DATA_END)); 1439 : 34 : + cur_page_offset= (uint32_t)ibb_page_size - BINLOG_PAGE_DATA_END; 1440 : 34 : + uint32_t payload= IBB_HEADER_PAGE_SIZE - BINLOG_PAGE_CHECKSUM; 1441 : 34 : + int4store(page_buf + payload, my_crc32c(0, page_buf, payload)); 1442 : 34 : + fsp_binlog_extract_header_page(page_buf, &header); 1443 : 34 : + if (header.is_invalid) 1444 : : + { 1445 : 0 : + sql_print_error("InnoDB: Corrupt or invalid file header found during " 1446 : : + "recovery of file number %" PRIu64, cur_file_no); 1447 : 0 : + return !srv_force_recovery; 1448 : : + } 1449 : 34 : + if (header.is_empty) 1450 : : + { 1451 : 0 : + sql_print_error("InnoDB: Empty file header found during " 1452 : : + "recovery of file number %" PRIu64, cur_file_no); 1453 : 0 : + return !srv_force_recovery; 1454 : : + } 1455 : 34 : + if (header.file_no != cur_file_no) 1456 : : + { 1457 : 0 : + sql_print_error("InnoDB: Inconsistency in file header during recovery. " 1458 : : + "The header in file number %" PRIu64 " is for file " 1459 : : + "number %" PRIu64, cur_file_no, header.file_no); 1460 : 0 : + return !srv_force_recovery; 1461 : : + } 1462 : : + 1463 : 34 : + return false; 1464 : : + } 1465 : : + 1466 : 6156 : + cur_page_offset= offset + (uint32_t)size; 1467 : 6156 : + return false; 1468 : : +} 1469 : : + 1470 : : + 1471 : : +/** 1472 : : + Check if this is an InnoDB binlog file name. 1473 : : + Return the index/file_no if so. 1474 : : +*/ 1475 : : +bool 1476 : 111153 : +is_binlog_name(const char *name, uint64_t *out_idx) 1477 : : +{ 1478 : 111153 : + const size_t base_len= sizeof(BINLOG_NAME_BASE) - 1; // Length without '\0' terminator 1479 : 111153 : + const size_t ext_len= sizeof(BINLOG_NAME_EXT) - 1; 1480 : : + 1481 : 111153 : + if (0 != strncmp(name, BINLOG_NAME_BASE, base_len)) 1482 : 105713 : + return false; 1483 : 5440 : + size_t name_len= strlen(name); 1484 : 5440 : + if (name_len < base_len + 1 + ext_len) 1485 : 0 : + return false; 1486 : 5440 : + const char *ext_start= name + (name_len - ext_len); 1487 : 5440 : + if (0 != strcmp(ext_start, BINLOG_NAME_EXT)) 1488 : 900 : + return false; 1489 : 4540 : + if (!std::isdigit((unsigned char)(name[base_len]))) 1490 : 2 : + return false; 1491 : 4538 : + char *conv_end= nullptr; 1492 : 4538 : + unsigned long long idx= std::strtoull(name + base_len, &conv_end, 10); 1493 : 4538 : + if (idx == ULLONG_MAX || conv_end != ext_start) 1494 : 0 : + return false; 1495 : : + 1496 : 4538 : + *out_idx= (uint64_t)idx; 1497 : 4538 : + return true; 1498 : : +} 1499 : : + 1500 : : + 1501 : : +dberr_t 1502 : 11023 : +innodb_binlog_startup_init() 1503 : : +{ 1504 : 11023 : + dberr_t err= fsp_binlog_init(); 1505 : 11023 : + if (err != DB_SUCCESS) 1506 : 0 : + return err; 1507 : 11023 : + mysql_mutex_init(fsp_purge_binlog_mutex_key, &purge_binlog_mutex, nullptr); 1508 : 11023 : + binlog_full_state.init(); 1509 : 11023 : + binlog_diff_state.init(); 1510 : 11023 : + ibb_xa_xid_hash= new ibb_xid_hash(); 1511 : 11023 : + if (UNIV_UNLIKELY(!ibb_xa_xid_hash)) 1512 : : + { 1513 : 0 : + sql_print_error("InnoDB: Could not allocate memory for the internal " 1514 : : + "XID hash, cannot proceed"); 1515 : 0 : + return DB_OUT_OF_MEMORY; 1516 : : + } 1517 : : + 1518 : 11023 : + innodb_binlog_inited= 1; 1519 : 11023 : + return DB_SUCCESS; 1520 : : +} 1521 : : + 1522 : : + 1523 : : +static void 1524 : 1883 : +innodb_binlog_init_state() 1525 : : +{ 1526 : 1883 : + first_open_binlog_file_no= ~(uint64_t)0; 1527 : 9415 : + for (uint32_t i= 0; i < 4; ++i) 1528 : : + { 1529 : 7532 : + binlog_cur_end_offset[i].store(~(uint64_t)0, std::memory_order_relaxed); 1530 : 7532 : + binlog_cur_durable_offset[i].store(~(uint64_t)0, std::memory_order_relaxed); 1531 : : + } 1532 : 1883 : + last_created_binlog_file_no= ~(uint64_t)0; 1533 : 1883 : + earliest_binlog_file_no= ~(uint64_t)0; 1534 : 1883 : + total_binlog_used_size= 0; 1535 : : + active_binlog_file_no.store(~(uint64_t)0, std::memory_order_release); 1536 : : + ibb_file_hash.earliest_oob_ref.store(0, std::memory_order_relaxed); 1537 : 1883 : + binlog_cur_page_no= 0; 1538 : 1883 : + binlog_cur_page_offset= BINLOG_PAGE_DATA; 1539 : 1883 : + current_binlog_state_interval= 1540 : 1883 : + (uint64_t)(innodb_binlog_state_interval >> ibb_page_size_shift); 1541 : 1883 : + ut_a(innodb_binlog_state_interval == 1542 : : + (current_binlog_state_interval << ibb_page_size_shift)); 1543 : 1883 : + binlog_full_state.reset_nolock(); 1544 : 1883 : + binlog_diff_state.reset_nolock(); 1545 : 1883 : +} 1546 : : + 1547 : : + 1548 : : +/** Start the thread that pre-allocates new binlog files. */ 1549 : : +static void 1550 : 1883 : +start_binlog_prealloc_thread() 1551 : : +{ 1552 : 1883 : + prealloc_thread_end= false; 1553 : 1883 : + binlog_prealloc_thr_obj= std::thread{innodb_binlog_prealloc_thread}; 1554 : : + 1555 : 1883 : + mysql_mutex_lock(&active_binlog_mutex); 1556 : 3719 : + while (last_created_binlog_file_no == ~(uint64_t)0) { 1557 : : + /* Wait for the first binlog file to be available. */ 1558 : 1836 : + my_cond_wait(&active_binlog_cond, &active_binlog_mutex.m_mutex); 1559 : : + } 1560 : 1883 : + mysql_mutex_unlock(&active_binlog_mutex); 1561 : 1883 : +} 1562 : : + 1563 : : + 1564 : : +/** 1565 : : + Write the initial header record to the file and durably sync it to disk in 1566 : : + the binlog tablespace file and in the redo log. 1567 : : + 1568 : : + This is to ensure recovery can work correctly. This way, recovery will 1569 : : + always find a non-empty file with an initial lsn to start recovery from. 1570 : : + Except in the case where we crash right here; in this case recovery will 1571 : : + find no binlog files at all and will know to recover to the empty state 1572 : : + with no binlog files present. 1573 : : +*/ 1574 : : +static void 1575 : 1836 : +binlog_sync_initial() 1576 : : +{ 1577 : 1836 : + chunk_data_flush dummy_data; 1578 : 1836 : + mtr_t mtr{nullptr}; 1579 : 1836 : + LF_PINS *lf_pins= lf_hash_get_pins(&ibb_file_hash.hash); 1580 : 1836 : + ut_a(lf_pins); 1581 : 1836 : + mtr.start(); 1582 : 1836 : + fsp_binlog_write_rec(&dummy_data, &mtr, FSP_BINLOG_TYPE_FILLER, lf_pins); 1583 : 1836 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 1584 : 1836 : + mtr.commit(); 1585 : 1836 : + lf_hash_put_pins(lf_pins); 1586 : 1836 : + log_buffer_flush_to_disk(true); 1587 : 1836 : + binlog_page_fifo->flush_up_to(0, 0); 1588 : 1836 : + binlog_page_fifo->do_fdatasync(0); 1589 : 1836 : + ibb_pending_lsn_fifo.add_to_fifo(mtr.commit_lsn(), file_no, 1590 : 1836 : + binlog_cur_end_offset[file_no & 3].load(std::memory_order_relaxed)); 1591 : 1836 : +} 1592 : : + 1593 : : + 1594 : : +void 1595 : 189 : +ibb_set_max_size(size_t binlog_size) 1596 : : +{ 1597 : 189 : + uint64_t pages= binlog_size >> ibb_page_size_shift; 1598 : 189 : + if (UNIV_LIKELY(pages > (uint64_t)UINT32_MAX)) { 1599 : 0 : + pages= UINT32_MAX; 1600 : 0 : + sql_print_warning("Requested max_binlog_size is larger than the maximum " 1601 : : + "InnoDB tablespace size, truncated to " UINT64PF, 1602 : : + (pages << ibb_page_size_shift)); 1603 : 189 : + } else if (pages < 4) { 1604 : 2 : + pages= 4; 1605 : 2 : + sql_print_warning("Requested max_binlog_size is smaller than the minimum " 1606 : : + "size supported by InnoDB, truncated to " UINT64PF, 1607 : : + (pages << ibb_page_size_shift)); 1608 : : + } 1609 : 189 : + innodb_binlog_size_in_pages= (uint32_t)pages; 1610 : 189 : +} 1611 : : + 1612 : : + 1613 : : +/** 1614 : : + Open the InnoDB binlog implementation. 1615 : : + This is called from server binlog layer if the user configured the binlog to 1616 : : + use the innodb implementation (with --binlog-storage-engine=innodb). 1617 : : +*/ 1618 : : +bool 1619 : 169 : +innodb_binlog_init(size_t binlog_size, const char *directory, 1620 : : + HASH *recovery_hash) 1621 : : +{ 1622 : : + /** 1623 : : + The file_no from which we should start scanning to recover any prepare and 1624 : : + committed XID. 1625 : : + */ 1626 : 169 : + uint64_t recover_start_file_no= ~(uint64_t)0; 1627 : 169 : + uint64_t recover_start_offset= 0; 1628 : : + 1629 : 169 : + ibb_set_max_size(binlog_size); 1630 : 169 : + if (!directory || !directory[0]) 1631 : 155 : + directory= "."; 1632 : 14 : + else if (strlen(directory) + BINLOG_NAME_MAX_LEN > OS_FILE_MAX_PATH) 1633 : : + { 1634 : 0 : + sql_print_error("Specified binlog directory path '%s' is too long", 1635 : : + directory); 1636 : 0 : + return true; 1637 : : + } 1638 : 169 : + innodb_binlog_directory= directory; 1639 : : + 1640 : 169 : + innodb_binlog_init_state(); 1641 : 169 : + innodb_binlog_inited= 2; 1642 : : + 1643 : : + /* Find any existing binlog files and continue writing in them. */ 1644 : 169 : + int res= innodb_binlog_discover(); 1645 : 169 : + if (res < 0) 1646 : 0 : + return true; 1647 : 169 : + if (res > 0) 1648 : : + { 1649 : : + /* We are continuing from existing binlogs. Recover the binlog state. */ 1650 : 47 : + if (binlog_state_recover(&recover_start_file_no, 1651 : : + &recover_start_offset)) 1652 : 0 : + return true; 1653 : : + } 1654 : : + else 1655 : : + { 1656 : : + /* Starting new binlogs, no XA to recover. */ 1657 : 122 : + recover_start_file_no= ~(uint64_t)0; 1658 : 122 : + recover_start_offset= 0; 1659 : : + } 1660 : : + 1661 : 169 : + start_binlog_prealloc_thread(); 1662 : : + 1663 : 169 : + if (res <= 0) 1664 : : + { 1665 : : + /* 1666 : : + We are creating binlogs anew from scratch. 1667 : : + Write and fsync the initial file-header, so that recovery will know where 1668 : : + to start in case of a crash. 1669 : : + */ 1670 : 122 : + binlog_sync_initial(); 1671 : : + } 1672 : : + else 1673 : : + { 1674 : : + /* 1675 : : + Recover XIDs for pending 2pc/XA transactions (if any) by scanning 1676 : : + required part of binlog. 1677 : : + */ 1678 : 47 : + if (binlog_scan_for_xid(recover_start_file_no, recover_start_offset, 1679 : : + recovery_hash)) 1680 : 0 : + return true; 1681 : 47 : + LF_PINS *lf_pins= lf_hash_get_pins(&ibb_file_hash.hash); 1682 : 47 : + if (UNIV_UNLIKELY(!lf_pins)) 1683 : : + { 1684 : 0 : + sql_print_error("InnoDB: Out of memory while recovering pending XID"); 1685 : 0 : + return true; 1686 : : + } 1687 : 47 : + bool err= ibb_init_xid_hash(recovery_hash, lf_pins); 1688 : 47 : + lf_hash_put_pins(lf_pins); 1689 : 47 : + return err; 1690 : : + } 1691 : : + 1692 : 122 : + return false; 1693 : : +} 1694 : : + 1695 : : + 1696 : : +/** Compute the (so far) last and last-but-one binlog files found. */ 1697 : : +static void 1698 : 326 : +process_binlog_name(found_binlogs *bls, uint64_t idx, size_t size) 1699 : : +{ 1700 : 326 : + if (bls->num_found == 0) 1701 : : + { 1702 : 74 : + bls->earliest_file_no= idx; 1703 : 74 : + bls->total_size= size; 1704 : : + } 1705 : : + else 1706 : : + { 1707 : 252 : + if (idx < bls->earliest_file_no) 1708 : 167 : + bls->earliest_file_no= idx; 1709 : 252 : + bls->total_size+= size; 1710 : : + } 1711 : : + 1712 : 326 : + if (bls->num_found == 0 || 1713 : 252 : + idx > bls->last_file_no) { 1714 : 115 : + if (bls->num_found >= 1 && idx == bls->last_file_no + 1) { 1715 : 28 : + bls->prev_file_no= bls->last_file_no; 1716 : 28 : + bls->prev_size= bls->last_size; 1717 : 28 : + bls->num_found= 2; 1718 : : + } else { 1719 : 87 : + bls->num_found= 1; 1720 : : + } 1721 : 115 : + bls->last_file_no= idx; 1722 : 115 : + bls->last_size= size; 1723 : 211 : + } else if (bls->num_found == 1 && idx + 1 == bls->last_file_no) { 1724 : 69 : + bls->num_found= 2; 1725 : 69 : + bls->prev_file_no= idx; 1726 : 69 : + bls->prev_size= size; 1727 : : + } 1728 : 326 : +} 1729 : : + 1730 : : + 1731 : : +/** 1732 : : + Scan the binlog directory for binlog files. 1733 : : + Returns: 1734 : : + 1 Success 1735 : : + 0 Binlog directory not found 1736 : : + -1 Other error 1737 : : +*/ 1738 : : +static int 1739 : 198 : +scan_for_binlogs(const char *binlog_dir, found_binlogs *binlog_files, 1740 : : + bool error_if_missing) noexcept 1741 : : +{ 1742 : 198 : + MY_DIR *dir= my_dir(binlog_dir, MYF(MY_WANT_STAT)); 1743 : 198 : + if (!dir) 1744 : : + { 1745 : 0 : + if (my_errno != ENOENT || error_if_missing) 1746 : 0 : + sql_print_error("Could not read the binlog directory '%s', error code %d", 1747 : 0 : + binlog_dir, my_errno); 1748 : 0 : + return (my_errno == ENOENT ? 0 : -1); 1749 : : + } 1750 : : + 1751 : 198 : + binlog_files->num_found= 0; 1752 : 198 : + size_t num_entries= dir->number_of_files; 1753 : 198 : + fileinfo *entries= dir->dir_entry; 1754 : 3384 : + for (size_t i= 0; i < num_entries; ++i) { 1755 : 3186 : + const char *name= entries[i].name; 1756 : : + uint64_t idx; 1757 : 3186 : + if (!is_binlog_name(name, &idx)) 1758 : 2860 : + continue; 1759 : 326 : + process_binlog_name(binlog_files, idx, (size_t)entries[i].mystat->st_size); 1760 : : + } 1761 : 198 : + my_dirend(dir); 1762 : : + 1763 : 198 : + return 1; /* Success */ 1764 : : +} 1765 : : + 1766 : : + 1767 : : +static bool 1768 : 179 : +binlog_page_empty(const byte *page) 1769 : : +{ 1770 : 179 : + return page[BINLOG_PAGE_DATA] == 0; 1771 : : +} 1772 : : + 1773 : : + 1774 : : +/** 1775 : : + Find the last written position in the binlog file. 1776 : : + Do a binary search through the pages to find the last non-empty page, then 1777 : : + scan the page to find the place to start writing new binlog data. 1778 : : + 1779 : : + Returns: 1780 : : + 1 position found, output in *out_space, *out_page_no and *out_pos_in_page. 1781 : : + 0 binlog file is empty. 1782 : : + -1 error. 1783 : : +*/ 1784 : : + 1785 : : +static int 1786 : 75 : +find_pos_in_binlog(uint64_t file_no, size_t file_size, byte *page_buf, 1787 : : + uint32_t *out_page_no, uint32_t *out_pos_in_page, 1788 : : + binlog_header_data *out_header_data) 1789 : : +{ 1790 : 75 : + const uint32_t page_size= (uint32_t)ibb_page_size; 1791 : 75 : + const uint32_t page_size_shift= (uint32_t)ibb_page_size_shift; 1792 : 75 : + const uint32_t idx= file_no & 3; 1793 : : + char file_name[OS_FILE_MAX_PATH]; 1794 : : + uint32_t p_0, p_1, p_2, last_nonempty; 1795 : : + byte *p, *page_end; 1796 : : + bool ret; 1797 : : + 1798 : 75 : + *out_page_no= 0; 1799 : 75 : + *out_pos_in_page= BINLOG_PAGE_DATA; 1800 : 75 : + out_header_data->diff_state_interval= 0; 1801 : 75 : + out_header_data->is_invalid= true; 1802 : : + 1803 : 75 : + binlog_name_make(file_name, file_no); 1804 : 75 : + pfs_os_file_t fh= os_file_create(innodb_data_file_key, file_name, 1805 : : + OS_FILE_OPEN, OS_DATA_FILE, 1806 : : + srv_read_only_mode, &ret); 1807 : 75 : + if (!ret) { 1808 : 0 : + sql_print_warning("InnoDB: Unable to open file '%s'", file_name); 1809 : 0 : + return -1; 1810 : : + } 1811 : : + 1812 : 75 : + int res= crc32_pread_page(fh, page_buf, 0, MYF(MY_WME)); 1813 : 75 : + if (res <= 0) { 1814 : 0 : + os_file_close(fh); 1815 : 0 : + return -1; 1816 : : + } 1817 : 75 : + fsp_binlog_extract_header_page(page_buf, out_header_data); 1818 : 75 : + if (out_header_data->is_invalid) 1819 : : + { 1820 : 0 : + sql_print_error("InnoDB: Invalid or corrupt file header in file " 1821 : : + "'%s'", file_name); 1822 : 0 : + return -1; 1823 : : + } 1824 : 75 : + if (out_header_data->is_empty) { 1825 : 28 : + ret= 1826 : 28 : + fsp_binlog_open(file_name, fh, file_no, file_size, ~(uint32_t)0, nullptr); 1827 : 28 : + binlog_cur_durable_offset[idx].store(0, std::memory_order_relaxed); 1828 : 28 : + binlog_cur_end_offset[idx].store(0, std::memory_order_relaxed); 1829 : 28 : + return (ret ? -1 : 0); 1830 : : + } 1831 : 47 : + if (out_header_data->file_no != file_no) 1832 : : + { 1833 : 0 : + sql_print_error("InnoDB: Inconsistent file header in file '%s', " 1834 : : + "wrong file_no %" PRIu64, file_name, 1835 : : + out_header_data->file_no); 1836 : 0 : + return -1; 1837 : : + } 1838 : 47 : + last_nonempty= 0; 1839 : : + 1840 : : + /* 1841 : : + During the binary search, p_0-1 is the largest page number that is know to 1842 : : + be non-empty. And p_2 is the first page that is known to be empty. 1843 : : + */ 1844 : 47 : + p_0= 1; 1845 : 47 : + p_2= (uint32_t)(file_size / page_size); 1846 : : + for (;;) { 1847 : 226 : + if (p_0 == p_2) 1848 : 47 : + break; 1849 : 179 : + ut_ad(p_0 < p_2); 1850 : 179 : + p_1= (p_0 + p_2) / 2; 1851 : 179 : + res= crc32_pread_page(fh, page_buf, p_1, MYF(MY_WME)); 1852 : 179 : + if (res <= 0) { 1853 : 0 : + os_file_close(fh); 1854 : 0 : + return -1; 1855 : : + } 1856 : 179 : + if (binlog_page_empty(page_buf)) { 1857 : 100 : + p_2= p_1; 1858 : : + } else { 1859 : 79 : + p_0= p_1 + 1; 1860 : 79 : + last_nonempty= p_1; 1861 : : + } 1862 : : + } 1863 : : + /* At this point, p_0 == p_2 is the first empty page. */ 1864 : 47 : + ut_ad(p_0 >= 1); 1865 : : + 1866 : : + /* 1867 : : + This sometimes does an extra read, but as this is only during startup it 1868 : : + does not matter. 1869 : : + */ 1870 : 47 : + res= crc32_pread_page(fh, page_buf, last_nonempty, MYF(MY_WME)); 1871 : 47 : + if (res <= 0) { 1872 : 0 : + os_file_close(fh); 1873 : 0 : + return -1; 1874 : : + } 1875 : : + 1876 : : + /* Now scan the last page to find the position in it to continue. */ 1877 : 47 : + p= &page_buf[BINLOG_PAGE_DATA]; 1878 : 47 : + page_end= &page_buf[page_size - BINLOG_PAGE_DATA_END]; 1879 : 433 : + while (*p && p < page_end) { 1880 : 386 : + if (*p == FSP_BINLOG_TYPE_FILLER) { 1881 : 0 : + p= page_end; 1882 : 0 : + break; 1883 : : + } 1884 : 386 : + p += 3 + (((uint32_t)p[2] << 8) | ((uint32_t)p[1] & 0xff)); 1885 : 386 : + if(UNIV_UNLIKELY(p > page_end)) 1886 : : + { 1887 : 0 : + sql_print_error("InnoDB: Invalid record in file_no=%" PRIu64 1888 : : + " page_no=%u (invalid chunk length)", 1889 : : + file_no, last_nonempty); 1890 : 0 : + return -1; 1891 : : + } 1892 : : + } 1893 : : + 1894 : : + /* 1895 : : + Normalize the position, so that we store (page_no+1, BINLOG_PAGE_DATA) 1896 : : + and not (page_no, page_size - BINLOG_PAGE_DATA_END). 1897 : : + */ 1898 : : + byte *partial_page; 1899 : 47 : + if (p == page_end) 1900 : : + { 1901 : 2 : + *out_page_no= p_0; 1902 : 2 : + *out_pos_in_page= BINLOG_PAGE_DATA; 1903 : 2 : + partial_page= nullptr; 1904 : : + } 1905 : : + else 1906 : : + { 1907 : 45 : + *out_page_no= p_0 - 1; 1908 : 45 : + *out_pos_in_page= (uint32_t)(p - page_buf); 1909 : 45 : + partial_page= page_buf; 1910 : : + } 1911 : : + 1912 : 47 : + ret= fsp_binlog_open(file_name, fh, file_no, file_size, 1913 : : + *out_page_no, partial_page); 1914 : 47 : + uint64_t pos= (*out_page_no << page_size_shift) | *out_pos_in_page; 1915 : 47 : + binlog_cur_durable_offset[idx].store(pos, std::memory_order_relaxed); 1916 : 47 : + binlog_cur_end_offset[idx].store(pos, std::memory_order_relaxed); 1917 : 47 : + return ret ? -1 : 1; 1918 : : +} 1919 : : + 1920 : : + 1921 : : +static void 1922 : 47 : +binlog_discover_init(uint64_t file_no, uint64_t interval) 1923 : : +{ 1924 : : + active_binlog_file_no.store(file_no, std::memory_order_release); 1925 : : + ibb_file_hash.earliest_oob_ref.store(file_no, std::memory_order_relaxed); 1926 : 47 : + current_binlog_state_interval= interval; 1927 : 47 : + ibb_pending_lsn_fifo.init(file_no); 1928 : 47 : +} 1929 : : + 1930 : : + 1931 : : +/** 1932 : : + Returns: 1933 : : + -1 error 1934 : : + 0 No binlogs found 1935 : : + 1 Just one binlog file found 1936 : : + 2 Found two (or more) existing binlog files 1937 : : +*/ 1938 : : +static int 1939 : 169 : +innodb_binlog_discover() 1940 : : +{ 1941 : : + uint64_t file_no; 1942 : 169 : + const uint32_t page_size= (uint32_t)ibb_page_size; 1943 : 169 : + const uint32_t page_size_shift= (uint32_t)ibb_page_size_shift; 1944 : : + struct found_binlogs binlog_files; 1945 : : + binlog_header_data header; 1946 : : + 1947 : 169 : + int res= scan_for_binlogs(innodb_binlog_directory, &binlog_files, false); 1948 : 169 : + if (res <= 0) 1949 : : + { 1950 : 0 : + if (res == 0) 1951 : 0 : + ibb_pending_lsn_fifo.init(0); 1952 : 0 : + return res; 1953 : : + } 1954 : : + 1955 : : + /* 1956 : : + Now, if we found any binlog files, locate the point in one of them where 1957 : : + binlogging stopped, and where we should continue writing new binlog data. 1958 : : + */ 1959 : : + uint32_t page_no, prev_page_no, pos_in_page, prev_pos_in_page; 1960 : : + std::unique_ptr 1961 : 169 : + page_buf(static_cast(aligned_malloc(page_size, page_size)), 1962 : 169 : + &aligned_free); 1963 : 169 : + if (!page_buf) 1964 : 0 : + return -1; 1965 : 169 : + if (binlog_files.num_found >= 1) { 1966 : 47 : + earliest_binlog_file_no= binlog_files.earliest_file_no; 1967 : 47 : + total_binlog_used_size= binlog_files.total_size; 1968 : : + 1969 : 47 : + res= find_pos_in_binlog(binlog_files.last_file_no, 1970 : : + binlog_files.last_size, 1971 : : + page_buf.get(), &page_no, &pos_in_page, 1972 : : + &header); 1973 : 47 : + if (res < 0) { 1974 : 0 : + file_no= binlog_files.last_file_no; 1975 : 0 : + if (ibb_record_in_file_hash(file_no, ~(uint64_t)0, ~(uint64_t)0)) 1976 : 0 : + return -1; 1977 : 0 : + binlog_discover_init(file_no, innodb_binlog_state_interval); 1978 : 0 : + sql_print_warning("Binlog number " UINT64PF " could no be opened. " 1979 : : + "Starting a new binlog file from number " UINT64PF, 1980 : : + binlog_files.last_file_no, (file_no + 1)); 1981 : 0 : + return 0; 1982 : : + } 1983 : : + 1984 : 47 : + if (res > 0) { 1985 : : + /* Found start position in the last binlog file. */ 1986 : 19 : + file_no= binlog_files.last_file_no; 1987 : 19 : + if (ibb_record_in_file_hash(file_no, header.oob_ref_file_no, 1988 : : + header.xa_ref_file_no)) 1989 : 0 : + return -1; 1990 : 19 : + binlog_discover_init(file_no, header.diff_state_interval); 1991 : 19 : + binlog_cur_page_no= page_no; 1992 : 19 : + binlog_cur_page_offset= pos_in_page; 1993 : 19 : + sql_print_information("InnoDB: Continuing binlog number %" PRIu64 1994 : : + " from position %" PRIu64 ".", file_no, 1995 : 19 : + (((uint64_t)page_no << page_size_shift) 1996 : 19 : + | pos_in_page)); 1997 : 19 : + return binlog_files.num_found; 1998 : : + } 1999 : : + 2000 : : + /* res == 0, the last binlog is empty. */ 2001 : 28 : + if (ibb_record_in_file_hash(binlog_files.last_file_no, 2002 : : + ~(uint64_t)0, ~(uint64_t)0)) 2003 : 0 : + return -1; 2004 : 28 : + if (binlog_files.num_found >= 2) { 2005 : : + /* The last binlog is empty, try the previous one. */ 2006 : 28 : + res= find_pos_in_binlog(binlog_files.prev_file_no, 2007 : : + binlog_files.prev_size, 2008 : : + page_buf.get(), 2009 : : + &prev_page_no, &prev_pos_in_page, 2010 : : + &header); 2011 : 28 : + if (res < 0) { 2012 : 0 : + file_no= binlog_files.last_file_no; 2013 : 0 : + if (ibb_record_in_file_hash(file_no, ~(uint64_t)0, ~(uint64_t)0)) 2014 : 0 : + return -1; 2015 : 0 : + binlog_discover_init(file_no, innodb_binlog_state_interval); 2016 : 0 : + binlog_cur_page_no= page_no; 2017 : 0 : + binlog_cur_page_offset= pos_in_page; 2018 : 0 : + sql_print_warning("Binlog number " UINT64PF " could not be opened, " 2019 : : + "starting from binlog number " UINT64PF " instead", 2020 : : + binlog_files.prev_file_no, file_no); 2021 : 0 : + return 1; 2022 : : + } 2023 : 28 : + file_no= binlog_files.prev_file_no; 2024 : 28 : + if (ibb_record_in_file_hash(file_no, header.oob_ref_file_no, 2025 : : + header.xa_ref_file_no)) 2026 : 0 : + return -1; 2027 : 28 : + binlog_discover_init(file_no, header.diff_state_interval); 2028 : 28 : + binlog_cur_page_no= prev_page_no; 2029 : 28 : + binlog_cur_page_offset= prev_pos_in_page; 2030 : 28 : + sql_print_information("InnoDB: Continuing binlog number %" PRIu64 2031 : : + " from position %" PRIu64 ".", file_no, 2032 : 28 : + (((uint64_t)prev_page_no << page_size_shift) | 2033 : 28 : + prev_pos_in_page)); 2034 : 28 : + return binlog_files.num_found; 2035 : : + } 2036 : : + 2037 : : + /* Just one empty binlog file found. */ 2038 : 0 : + file_no= binlog_files.last_file_no; 2039 : 0 : + if (ibb_record_in_file_hash(file_no, ~(uint64_t)0, ~(uint64_t)0)) 2040 : 0 : + return -1; 2041 : 0 : + binlog_discover_init(file_no, innodb_binlog_state_interval); 2042 : 0 : + binlog_cur_page_no= page_no; 2043 : 0 : + binlog_cur_page_offset= pos_in_page; 2044 : 0 : + sql_print_information("InnoDB: Continuing binlog number %" PRIu64 " from " 2045 : : + "position %u.", file_no, BINLOG_PAGE_DATA); 2046 : 0 : + return binlog_files.num_found; 2047 : : + } 2048 : : + 2049 : : + /* No binlog files found, start from scratch. */ 2050 : 122 : + file_no= 0; 2051 : 122 : + earliest_binlog_file_no= 0; 2052 : : + ibb_file_hash.earliest_oob_ref.store(0, std::memory_order_relaxed); 2053 : 122 : + total_binlog_used_size= 0; 2054 : 122 : + ibb_pending_lsn_fifo.init(0); 2055 : 122 : + current_binlog_state_interval= innodb_binlog_state_interval; 2056 : 122 : + sql_print_information("InnoDB: Starting a new binlog from file number %" 2057 : : + PRIu64 ".", file_no); 2058 : 122 : + return 0; 2059 : 169 : +} 2060 : : + 2061 : : + 2062 : 12383 : +void innodb_binlog_close(bool shutdown) 2063 : : +{ 2064 : 12383 : + if (innodb_binlog_inited >= 2) 2065 : : + { 2066 : 1875 : + if (binlog_prealloc_thr_obj.joinable()) { 2067 : 1875 : + mysql_mutex_lock(&active_binlog_mutex); 2068 : 1875 : + prealloc_thread_end= true; 2069 : 1875 : + pthread_cond_signal(&active_binlog_cond); 2070 : 1875 : + mysql_mutex_unlock(&active_binlog_mutex); 2071 : 1875 : + binlog_prealloc_thr_obj.join(); 2072 : : + } 2073 : : + 2074 : 1875 : + uint64_t file_no= first_open_binlog_file_no; 2075 : 1875 : + if (file_no != ~(uint64_t)0) { 2076 : 1875 : + if (file_no <= last_created_binlog_file_no) { 2077 : 1875 : + fsp_binlog_tablespace_close(file_no); 2078 : 1875 : + if (file_no + 1 <= last_created_binlog_file_no) { 2079 : 1875 : + fsp_binlog_tablespace_close(file_no + 1); 2080 : : + } 2081 : : + } 2082 : : + } 2083 : : + } 2084 : : + 2085 : 12383 : + if (shutdown && innodb_binlog_inited >= 1) 2086 : : + { 2087 : 9425 : + delete ibb_xa_xid_hash; 2088 : 9425 : + binlog_diff_state.free(); 2089 : 9425 : + binlog_full_state.free(); 2090 : 9425 : + fsp_binlog_shutdown(); 2091 : 9425 : + mysql_mutex_destroy(&purge_binlog_mutex); 2092 : : + } 2093 : 12383 : +} 2094 : : + 2095 : : + 2096 : : +/** 2097 : : + Background thread to close old binlog tablespaces and pre-allocate new ones. 2098 : : +*/ 2099 : : +static void 2100 : 1883 : +innodb_binlog_prealloc_thread() 2101 : : +{ 2102 : 1883 : + my_thread_init(); 2103 : : +#ifdef UNIV_PFS_THREAD 2104 : 1883 : + pfs_register_thread(binlog_prealloc_thread_key); 2105 : : +#endif 2106 : 1883 : + LF_PINS *lf_pins= lf_hash_get_pins(&ibb_file_hash.hash); 2107 : 1883 : + ut_a(lf_pins); 2108 : : + 2109 : 1883 : + mysql_mutex_lock(&active_binlog_mutex); 2110 : : + while (1) 2111 : : + { 2112 : 12785 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 2113 : 12785 : + uint64_t first_open= first_open_binlog_file_no; 2114 : : + 2115 : : + /* Pre-allocate the next tablespace (if not done already). */ 2116 : 12785 : + uint64_t last_created= last_created_binlog_file_no; 2117 : 12785 : + if (last_created <= active && last_created <= first_open) { 2118 : 5086 : + ut_ad(last_created == active); 2119 : 5086 : + ut_ad(last_created == first_open || first_open == ~(uint64_t)0); 2120 : : + /* 2121 : : + Note: `last_created` is initialized to ~0, so incrementing it here 2122 : : + makes us start from binlog file 0. 2123 : : + */ 2124 : 5086 : + ++last_created; 2125 : 5086 : + mysql_mutex_unlock(&active_binlog_mutex); 2126 : : + 2127 : 5086 : + mysql_mutex_lock(&purge_binlog_mutex); 2128 : 5086 : + uint32_t size_in_pages= innodb_binlog_size_in_pages; 2129 : 5086 : + dberr_t res2= fsp_binlog_tablespace_create(last_created, size_in_pages, 2130 : : + lf_pins); 2131 : 5086 : + if (earliest_binlog_file_no == ~(uint64_t)0) 2132 : 1714 : + earliest_binlog_file_no= last_created; 2133 : 5086 : + total_binlog_used_size+= (size_in_pages << ibb_page_size_shift); 2134 : : + 2135 : 5086 : + innodb_binlog_autopurge(first_open, lf_pins); 2136 : 5086 : + mysql_mutex_unlock(&purge_binlog_mutex); 2137 : : + 2138 : 5086 : + mysql_mutex_lock(&active_binlog_mutex); 2139 : 5086 : + ut_a(res2 == DB_SUCCESS); 2140 : 5086 : + last_created_binlog_file_no= last_created; 2141 : : + 2142 : : + /* If we created the initial tablespace file, make it the active one. */ 2143 : 5086 : + ut_ad(active < ~(uint64_t)0 || last_created == 0); 2144 : 5086 : + if (active == ~(uint64_t)0) { 2145 : 1836 : + binlog_cur_end_offset[last_created & 3]. 2146 : 1836 : + store(0, std::memory_order_release); 2147 : 1836 : + binlog_cur_durable_offset[last_created & 3] 2148 : 1836 : + .store(0, std::memory_order_release); 2149 : : + active_binlog_file_no.store(last_created, std::memory_order_relaxed); 2150 : : + ibb_file_hash.earliest_oob_ref.store(last_created, 2151 : : + std::memory_order_relaxed); 2152 : : + } 2153 : 5086 : + if (first_open == ~(uint64_t)0) 2154 : 1836 : + first_open_binlog_file_no= first_open= last_created; 2155 : : + 2156 : 5086 : + pthread_cond_signal(&active_binlog_cond); 2157 : 5086 : + continue; /* Re-start loop after releasing/reacquiring mutex. */ 2158 : 5086 : + } 2159 : : + 2160 : : + /* 2161 : : + Flush out to disk and close any binlog tablespace that has been 2162 : : + completely written. 2163 : : + */ 2164 : 7699 : + if (first_open < active) { 2165 : 1395 : + ut_ad(first_open == active - 1); 2166 : 1395 : + mysql_mutex_unlock(&active_binlog_mutex); 2167 : 1395 : + fsp_binlog_tablespace_close(active - 1); 2168 : 1395 : + mysql_mutex_lock(&active_binlog_mutex); 2169 : 1395 : + first_open_binlog_file_no= first_open + 1; 2170 : 1395 : + continue; /* Re-start loop after releasing/reacquiring mutex. */ 2171 : : + } 2172 : : + 2173 : : + /* Exit thread at server shutdown. */ 2174 : 6304 : + if (prealloc_thread_end) 2175 : 1875 : + break; 2176 : 4429 : + my_cond_wait(&active_binlog_cond, &active_binlog_mutex.m_mutex); 2177 : : + 2178 : 10902 : + } 2179 : 1875 : + mysql_mutex_unlock(&active_binlog_mutex); 2180 : : + 2181 : 1875 : + lf_hash_put_pins(lf_pins); 2182 : 1875 : + my_thread_end(); 2183 : : + 2184 : : +#ifdef UNIV_PFS_THREAD 2185 : 1875 : + pfs_delete_thread(); 2186 : : +#endif 2187 : 1875 : +} 2188 : : + 2189 : : + 2190 : : +bool 2191 : 3231 : +ibb_write_header_page(mtr_t *mtr, uint64_t file_no, uint64_t file_size_in_pages, 2192 : : + lsn_t start_lsn, uint64_t gtid_state_interval_in_pages, 2193 : : + LF_PINS *pins) 2194 : : +{ 2195 : : + fsp_binlog_page_entry *block; 2196 : : + uint32_t used_bytes; 2197 : : + 2198 : 3231 : + block= binlog_page_fifo->create_page(file_no, 0); 2199 : 3231 : + if (UNIV_UNLIKELY(!block)) 2200 : 0 : + return true; 2201 : 3231 : + byte *ptr= &block->page_buf()[0]; 2202 : : + uint64_t oob_ref_file_no= 2203 : 3231 : + ibb_file_hash.earliest_oob_ref.load(std::memory_order_relaxed); 2204 : : + uint64_t xa_ref_file_no= 2205 : 3231 : + ibb_file_hash.earliest_xa_ref.load(std::memory_order_relaxed); 2206 : 3231 : + ibb_file_hash.update_refs(file_no, pins, oob_ref_file_no, xa_ref_file_no); 2207 : : + 2208 : 3231 : + int4store(ptr, IBB_MAGIC); 2209 : 3231 : + int4store(ptr + 4, ibb_page_size_shift); 2210 : 3231 : + int4store(ptr + 8, IBB_FILE_VERS_MAJOR); 2211 : 3231 : + int4store(ptr + 12, IBB_FILE_VERS_MINOR); 2212 : 3231 : + int8store(ptr + 16, file_no); 2213 : 3231 : + int8store(ptr + 24, file_size_in_pages); 2214 : 3231 : + int8store(ptr + 32, start_lsn); 2215 : 3231 : + int8store(ptr + 40, gtid_state_interval_in_pages); 2216 : 3231 : + int8store(ptr + 48, oob_ref_file_no); 2217 : 3231 : + int8store(ptr + 56, xa_ref_file_no); 2218 : 3231 : + used_bytes= IBB_BINLOG_HEADER_SIZE; 2219 : 3231 : + ut_ad(ibb_page_size >= IBB_HEADER_PAGE_SIZE); 2220 : 3231 : + memset(ptr + used_bytes, 0, ibb_page_size - (used_bytes + BINLOG_PAGE_CHECKSUM)); 2221 : : + /* 2222 : : + For future expansion with configurable page size: 2223 : : + Write a CRC32 at the end of the minimal page size. This way, the header 2224 : : + page can be read and checksummed without knowing the page size used in 2225 : : + the file, and then the actual page size can be obtained from the header 2226 : : + page. 2227 : : + */ 2228 : 3231 : + const uint32_t payload= IBB_HEADER_PAGE_SIZE - BINLOG_PAGE_CHECKSUM; 2229 : 3231 : + int4store(ptr + payload, my_crc32c(0, ptr, payload)); 2230 : : + 2231 : 3231 : + fsp_log_header_page(mtr, block, file_no, used_bytes); 2232 : 3231 : + binlog_page_fifo->release_page_mtr(block, mtr); 2233 : : + 2234 : 3231 : + return false; // No error 2235 : : +} 2236 : : + 2237 : : + 2238 : : +__attribute__((noinline)) 2239 : : +static ssize_t 2240 : 4409 : +serialize_gtid_state(rpl_binlog_state_base *state, byte *buf, size_t buf_size) 2241 : : + noexcept 2242 : : +{ 2243 : 4409 : + unsigned char *p= (unsigned char *)buf; 2244 : : + /* 2245 : : + 1 uint64_t for the number of entries in the state stored. 2246 : : + 1 uint64_t for the XA references file_no. 2247 : : + 2 uint32_t + 1 uint64_t for at least one GTID. 2248 : : + */ 2249 : 4409 : + ut_ad(buf_size >= 2*COMPR_INT_MAX32 + 3*COMPR_INT_MAX64); 2250 : 4409 : + p= compr_int_write(p, state->count_nolock()); 2251 : : + uint64_t xa_ref_file_no= 2252 : 4409 : + ibb_file_hash.earliest_xa_ref.load(std::memory_order_relaxed); 2253 : : + /* Write 1 +file_no, so that 0 (1 + ~0) means "no reference". */ 2254 : 4409 : + p= compr_int_write(p, xa_ref_file_no + 1); 2255 : 4409 : + unsigned char * const pmax= 2256 : 4409 : + p + (buf_size - (2*COMPR_INT_MAX32 + COMPR_INT_MAX64)); 2257 : : + 2258 : 4409 : + if (state->iterate( 2259 : 621802 : + [pmax, &p] (const rpl_gtid *gtid) { 2260 : 155497 : + if (UNIV_UNLIKELY(p > pmax)) 2261 : 62 : + return true; 2262 : 155435 : + p= compr_int_write(p, gtid->domain_id); 2263 : 155435 : + p= compr_int_write(p, gtid->server_id); 2264 : 155435 : + p= compr_int_write(p, gtid->seq_no); 2265 : 155435 : + return false; 2266 : : + })) 2267 : 62 : + return -1; 2268 : : + else 2269 : 4347 : + return p - (unsigned char *)buf; 2270 : : +} 2271 : : + 2272 : : + 2273 : : +bool 2274 : 4347 : +binlog_gtid_state(rpl_binlog_state_base *state, mtr_t *mtr, 2275 : : + fsp_binlog_page_entry * &block, uint32_t &page_no, 2276 : : + uint32_t &page_offset, uint64_t file_no) 2277 : : +{ 2278 : : + /* 2279 : : + Use a small, efficient stack-allocated buffer by default, falling back to 2280 : : + malloc() if needed for large GTID state. 2281 : : + */ 2282 : : + byte small_buf[192]; 2283 : : + byte *buf, *alloced_buf; 2284 : 4347 : + uint32_t block_page_no= ~(uint32_t)0; 2285 : 4347 : + block= nullptr; 2286 : : + 2287 : 4347 : + ssize_t used_bytes= serialize_gtid_state(state, small_buf, sizeof(small_buf)); 2288 : 4347 : + if (used_bytes >= 0) 2289 : : + { 2290 : 4285 : + buf= small_buf; 2291 : 4285 : + alloced_buf= nullptr; 2292 : : + } 2293 : : + else 2294 : : + { 2295 : 62 : + size_t buf_size= 2*COMPR_INT_MAX64 + 2296 : 62 : + state->count_nolock() * (2*COMPR_INT_MAX32 + COMPR_INT_MAX64); 2297 : 124 : + alloced_buf= static_cast(ut_malloc(buf_size, mem_key_binlog)); 2298 : 62 : + if (UNIV_UNLIKELY(!alloced_buf)) 2299 : 0 : + return true; 2300 : 62 : + buf= alloced_buf; 2301 : 62 : + used_bytes= serialize_gtid_state(state, buf, buf_size); 2302 : 62 : + if (UNIV_UNLIKELY(used_bytes < 0)) 2303 : : + { 2304 : 0 : + ut_ad(0 /* Shouldn't happen, as we allocated maximum needed size. */); 2305 : 0 : + ut_free(alloced_buf); 2306 : 0 : + return true; 2307 : : + } 2308 : : + } 2309 : : + 2310 : 4347 : + const uint32_t page_size= (uint32_t)ibb_page_size; 2311 : 4347 : + const uint32_t page_room= page_size - (BINLOG_PAGE_DATA + BINLOG_PAGE_DATA_END); 2312 : 4347 : + uint32_t needed_pages= (uint32_t)((used_bytes + page_room - 1) / page_room); 2313 : : + 2314 : : + /* For now, GTID state always at the start of a page. */ 2315 : 4347 : + ut_ad(page_offset == BINLOG_PAGE_DATA); 2316 : : + /* Page 0 is reserved for the header page. */ 2317 : 4347 : + ut_ad(page_no != 0); 2318 : : + 2319 : : + /* 2320 : : + Only write the GTID state record if there is room for actual event data 2321 : : + afterwards. There is no point in using space to allow fast search to a 2322 : : + point if there is no data to search for after that point. 2323 : : + */ 2324 : 4347 : + if (page_no + needed_pages < binlog_page_fifo->size_in_pages(file_no)) 2325 : : + { 2326 : 4347 : + byte cont_flag= 0; 2327 : 8768 : + while (used_bytes > 0) 2328 : : + { 2329 : 4421 : + ut_ad(page_no < binlog_page_fifo->size_in_pages(file_no)); 2330 : 4421 : + if (block) 2331 : 74 : + binlog_page_fifo->release_page_mtr(block, mtr); 2332 : 4421 : + block_page_no= page_no; 2333 : 4421 : + block= binlog_page_fifo->create_page(file_no, block_page_no); 2334 : 4421 : + if (UNIV_UNLIKELY(!block)) 2335 : 0 : + return true; 2336 : 4421 : + page_offset= BINLOG_PAGE_DATA; 2337 : 4421 : + byte *ptr= page_offset + &block->page_buf()[0]; 2338 : 4421 : + uint32_t chunk= (uint32_t)used_bytes; 2339 : 4421 : + byte last_flag= FSP_BINLOG_FLAG_LAST; 2340 : 4421 : + if (chunk > page_room - 3) { 2341 : 74 : + last_flag= 0; 2342 : 74 : + chunk= page_room - 3; 2343 : 74 : + ++page_no; 2344 : : + } 2345 : 4421 : + ptr[0]= FSP_BINLOG_TYPE_GTID_STATE | cont_flag | last_flag; 2346 : 4421 : + ptr[1] = (byte)chunk & 0xff; 2347 : 4421 : + ptr[2] = (byte)(chunk >> 8); 2348 : 4421 : + ut_ad(chunk <= 0xffff); 2349 : 4421 : + memcpy(ptr+3, buf, chunk); 2350 : 4421 : + fsp_log_binlog_write(mtr, block, file_no, block_page_no, page_offset, 2351 : 4421 : + (uint32)(chunk+3)); 2352 : 4421 : + page_offset+= chunk + 3; 2353 : 4421 : + buf+= chunk; 2354 : 4421 : + used_bytes-= chunk; 2355 : 4421 : + cont_flag= FSP_BINLOG_FLAG_CONT; 2356 : : + } 2357 : : + 2358 : 4347 : + if (page_offset == page_size - BINLOG_PAGE_DATA_END) { 2359 : 0 : + if (block) 2360 : 0 : + binlog_page_fifo->release_page_mtr(block, mtr); 2361 : 0 : + block= nullptr; 2362 : 0 : + block_page_no= ~(uint32_t)0; 2363 : 0 : + page_offset= BINLOG_PAGE_DATA; 2364 : 0 : + ++page_no; 2365 : : + } 2366 : : + } 2367 : 4347 : + ut_free(alloced_buf); 2368 : : + 2369 : : + /* Make sure we return a page for caller to write the main event data into. */ 2370 : 4347 : + if (UNIV_UNLIKELY(!block)) { 2371 : 0 : + block= binlog_page_fifo->create_page(file_no, page_no); 2372 : 0 : + if (UNIV_UNLIKELY(!block)) 2373 : 0 : + return true; 2374 : : + } 2375 : : + 2376 : 4347 : + return false; // No error 2377 : : +} 2378 : : + 2379 : : + 2380 : : +/** 2381 : : + Read a binlog state record. The passed in STATE object is updated with the 2382 : : + state read. 2383 : : + 2384 : : + Returns: 2385 : : + 1 State record found 2386 : : + 0 No state record found 2387 : : + -1 Error 2388 : : +*/ 2389 : : +static int 2390 : 3828 : +read_gtid_state(binlog_chunk_reader *chunk_reader, 2391 : : + rpl_binlog_state_base *state, 2392 : : + uint64_t *out_xa_ref_file_no) noexcept 2393 : : +{ 2394 : : + byte buf[256]; 2395 : : + static_assert(sizeof(buf) >= 2*COMPR_INT_MAX64 + 6*COMPR_INT_MAX64, 2396 : : + "buf must hold at least 2 GTIDs"); 2397 : 3828 : + int res= chunk_reader->read_data(buf, sizeof(buf), true); 2398 : 3828 : + if (UNIV_UNLIKELY(res < 0)) 2399 : 0 : + return -1; 2400 : 3828 : + if (res == 0 || chunk_reader->cur_type() != FSP_BINLOG_TYPE_GTID_STATE) 2401 : 2 : + return 0; 2402 : 3826 : + const byte *p= buf; 2403 : 3826 : + const byte *p_end= buf + res; 2404 : : + 2405 : : + /* Read the number of GTIDs in the gtid state record. */ 2406 : 3826 : + std::pair v_and_p= compr_int_read(buf); 2407 : 3826 : + p= v_and_p.second; 2408 : 3826 : + if (UNIV_UNLIKELY(p > p_end)) 2409 : 0 : + return -1; 2410 : 3826 : + uint64_t num_gtid= v_and_p.first; 2411 : : + /* 2412 : : + Read the earliest file_no containing pending XA if any. 2413 : : + Note that unsigned underflow means 0 - 1 becomes ~0, as required. 2414 : : + */ 2415 : 3826 : + v_and_p= compr_int_read(p); 2416 : 3826 : + p= v_and_p.second; 2417 : 3826 : + if (UNIV_UNLIKELY(p > p_end)) 2418 : 0 : + return -1; 2419 : 3826 : + *out_xa_ref_file_no= v_and_p.first - 1; 2420 : : + 2421 : : + /* Read each GTID one by one and add into the state. */ 2422 : 197824 : + for (uint64_t count= num_gtid; count > 0; --count) 2423 : : + { 2424 : 193998 : + ptrdiff_t remain= p_end - p; 2425 : : + /* Read more data as needed to ensure we have read a full GTID. */ 2426 : 357990 : + if (UNIV_UNLIKELY(!chunk_reader->end_of_record()) && 2427 : 163992 : + UNIV_UNLIKELY(remain < 3*COMPR_INT_MAX64)) 2428 : : + { 2429 : 7806 : + memmove(buf, p, remain); 2430 : 7806 : + res= chunk_reader->read_data(buf + remain, (int)(sizeof(buf) - remain), 2431 : : + true); 2432 : 7806 : + if (UNIV_UNLIKELY(res < 0)) 2433 : 0 : + return -1; 2434 : 7806 : + p= buf; 2435 : 7806 : + p_end= p + remain + res; 2436 : 7806 : + remain+= res; 2437 : : + } 2438 : : + rpl_gtid gtid; 2439 : 193998 : + if (UNIV_UNLIKELY(p >= p_end)) 2440 : 0 : + return -1; 2441 : 193998 : + v_and_p= compr_int_read(p); 2442 : 193998 : + if (UNIV_UNLIKELY(v_and_p.first > UINT32_MAX)) 2443 : 0 : + return -1; 2444 : 193998 : + gtid.domain_id= (uint32_t)v_and_p.first; 2445 : 193998 : + p= v_and_p.second; 2446 : 193998 : + if (UNIV_UNLIKELY(p >= p_end)) 2447 : 0 : + return -1; 2448 : 193998 : + v_and_p= compr_int_read(p); 2449 : 193998 : + if (UNIV_UNLIKELY(v_and_p.first > UINT32_MAX)) 2450 : 0 : + return -1; 2451 : 193998 : + gtid.server_id= (uint32_t)v_and_p.first; 2452 : 193998 : + p= v_and_p.second; 2453 : 193998 : + if (UNIV_UNLIKELY(p >= p_end)) 2454 : 0 : + return -1; 2455 : 193998 : + v_and_p= compr_int_read(p); 2456 : 193998 : + gtid.seq_no= v_and_p.first; 2457 : 193998 : + p= v_and_p.second; 2458 : 193998 : + if (UNIV_UNLIKELY(p > p_end)) 2459 : 0 : + return -1; 2460 : 193998 : + if (state->update_nolock(>id)) 2461 : 0 : + return -1; 2462 : : + } 2463 : : + 2464 : : + /* 2465 : : + For now, we expect no more data. 2466 : : + Later it could be extended, as we store (and read) the count of GTIDs. 2467 : : + */ 2468 : 3826 : + ut_ad(p == p_end); 2469 : : + 2470 : 3826 : + return 1; 2471 : : +} 2472 : : + 2473 : : + 2474 : : +/** 2475 : : + Recover the GTID binlog state at startup. 2476 : : + Read the full binlog state at the start of the current binlog file, as well 2477 : : + as the last differential binlog state on top, if any. Then scan from there to 2478 : : + the end to obtain the exact current GTID binlog state. 2479 : : + 2480 : : + Return false if ok, true if error. 2481 : : +*/ 2482 : : +static bool 2483 : 47 : +binlog_state_recover(uint64_t *out_xa_file_no, uint64_t *out_xa_offset) 2484 : : +{ 2485 : 47 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 2486 : 47 : + uint64_t diff_state_interval= current_binlog_state_interval; 2487 : 47 : + uint32_t page_no= 1; 2488 : : + 2489 : 47 : + *out_xa_file_no= earliest_binlog_file_no; 2490 : 47 : + *out_xa_offset= (uint64_t)1 << ibb_page_size_shift; 2491 : 47 : + binlog_chunk_reader chunk_reader(binlog_cur_end_offset); 2492 : : + byte *page_buf= 2493 : 94 : + static_cast(ut_malloc(ibb_page_size, mem_key_binlog)); 2494 : 47 : + if (!page_buf) 2495 : 0 : + return true; 2496 : 47 : + chunk_reader.set_page_buf(page_buf); 2497 : 47 : + *out_xa_offset= page_no << ibb_page_size_shift; 2498 : 47 : + chunk_reader.seek(active, *out_xa_offset); 2499 : 47 : + int res= read_gtid_state(&chunk_reader, &binlog_full_state, out_xa_file_no); 2500 : 47 : + if (res < 0) 2501 : : + { 2502 : 0 : + ut_free(page_buf); 2503 : 0 : + return true; 2504 : : + } 2505 : 47 : + if (diff_state_interval == 0) 2506 : : + { 2507 : 0 : + sql_print_warning("Invalid differential binlog state interval " UINT64PF 2508 : : + " found in binlog file, ignoring", diff_state_interval); 2509 : : + } 2510 : : + else 2511 : : + { 2512 : 47 : + page_no= (uint32_t)(binlog_cur_page_no - 2513 : 47 : + (binlog_cur_page_no % diff_state_interval)); 2514 : 49 : + while (page_no > 1) 2515 : : + { 2516 : 20 : + *out_xa_offset= page_no << ibb_page_size_shift; 2517 : 20 : + chunk_reader.seek(active, *out_xa_offset); 2518 : 20 : + res= read_gtid_state(&chunk_reader, &binlog_full_state, out_xa_file_no); 2519 : 20 : + if (res > 0) 2520 : 18 : + break; 2521 : 2 : + page_no-= (uint32_t)diff_state_interval; 2522 : : + } 2523 : : + } 2524 : 47 : + ut_free(page_buf); 2525 : 47 : + if (UNIV_LIKELY(*out_xa_file_no == ~(uint64_t)0)) 2526 : : + { 2527 : : + /* 2528 : : + If there were no XID references active at the last state record written, 2529 : : + then recovery only needs to scan from that point on. 2530 : : + */ 2531 : 39 : + *out_xa_file_no= active; 2532 : : + } 2533 : : + 2534 : : + ha_innodb_binlog_reader reader(false, active, 2535 : 47 : + page_no << ibb_page_size_shift); 2536 : 47 : + return binlog_recover_gtid_state(&binlog_full_state, &reader); 2537 : 47 : +} 2538 : : + 2539 : : + 2540 : : +static bool 2541 : 92 : +ibb_recv_record_update(HASH *hash, ibb_binlog_xid_info *info, uint64_t file_no) 2542 : : +{ 2543 : : + /* Delete any existing entry for this XID before inserting the newer one. */ 2544 : 92 : + size_t key_len= 0; 2545 : 92 : + const uchar *key_ptr= info->get_key(info, &key_len, 1); 2546 : 92 : + uchar *rec= my_hash_search(hash, key_ptr, key_len); 2547 : 92 : + if (rec != nullptr) 2548 : 28 : + my_hash_delete(hash, rec); 2549 : 92 : + if (my_hash_insert(hash, (const uchar *)info)) 2550 : : + { 2551 : 0 : + sql_print_error("InnoDB: Out of memory while scanning file_no=%" PRIu64, 2552 : : + file_no); 2553 : 0 : + delete info; 2554 : 0 : + return true; 2555 : : + } 2556 : 92 : + return false; 2557 : : +} 2558 : : + 2559 : : + 2560 : : +static bool 2561 : 52 : +ibb_recv_record_prepare(HASH *hash, uint64_t file_no, 2562 : : + const byte *rec_data, int data_len) 2563 : : +{ 2564 : 52 : + const byte *p= rec_data; 2565 : : + 2566 : 52 : + uchar engine_count= *p++; 2567 : 52 : + long formatID= uint4korr(p); 2568 : 52 : + p+= 4; 2569 : 52 : + byte gtrid_length= *p++; 2570 : 52 : + byte bqual_length= *p++; 2571 : 52 : + if (UNIV_UNLIKELY(gtrid_length > 64) || 2572 : 52 : + UNIV_UNLIKELY(bqual_length > 64)) 2573 : : + { 2574 : 0 : + sql_print_error("InnoDB: Corrupt prepare record found in file_no=%" PRIu64 2575 : : + ", invalid XID lengths %u/%u", file_no, 2576 : : + (uint)gtrid_length, (uint)bqual_length); 2577 : 0 : + return true; 2578 : : + } 2579 : 52 : + const char *xid_data= reinterpret_cast(p); 2580 : 52 : + p+= gtrid_length + bqual_length; 2581 : 52 : + std::pair v_and_p; 2582 : 52 : + v_and_p= compr_int_read(p); 2583 : 52 : + uint64_t num_oob_nodes= v_and_p.first; 2584 : 52 : + p= v_and_p.second; 2585 : 52 : + v_and_p= compr_int_read(p); 2586 : 52 : + uint64_t first_oob_file_no= v_and_p.first; 2587 : 52 : + p= v_and_p.second; 2588 : 52 : + v_and_p= compr_int_read(p); 2589 : 52 : + uint64_t first_oob_offset= v_and_p.first; 2590 : 52 : + p= v_and_p.second; 2591 : 52 : + v_and_p= compr_int_read(p); 2592 : 52 : + uint64_t last_oob_file_no= v_and_p.first; 2593 : 52 : + p= v_and_p.second; 2594 : 52 : + v_and_p= compr_int_read(p); 2595 : 52 : + uint64_t last_oob_offset= v_and_p.first; 2596 : 52 : + p= v_and_p.second; 2597 : 52 : + if ((int)(p - rec_data) > data_len) 2598 : : + { 2599 : 0 : + sql_print_error("InnoDB: Corrupt prepare record found in file_no=%" PRIu64 2600 : : + ", only %d bytes but expected %u", file_no, 2601 : 0 : + data_len, (uint)(p - rec_data)); 2602 : 0 : + return true; 2603 : : + } 2604 : : + ibb_binlog_xid_info *xid_info= 2605 : 52 : + new ibb_binlog_xid_info(handler_binlog_xid_info::BINLOG_PREPARE, file_no); 2606 : 52 : + if (!xid_info) 2607 : : + { 2608 : 0 : + sql_print_error("InnoDB: Out of memory while scanning file_no=%" PRIu64, 2609 : : + file_no); 2610 : 0 : + return true; 2611 : : + } 2612 : 52 : + xid_info->xid.set(formatID, xid_data, gtrid_length, 2613 : 52 : + xid_data + bqual_length, bqual_length); 2614 : 52 : + xid_info->engine_count= engine_count; 2615 : 52 : + xid_info->num_oob_nodes= num_oob_nodes; 2616 : 52 : + xid_info->first_oob_file_no= first_oob_file_no; 2617 : 52 : + xid_info->first_oob_offset= first_oob_offset; 2618 : 52 : + xid_info->last_oob_file_no= last_oob_file_no; 2619 : 52 : + xid_info->last_oob_offset= last_oob_offset; 2620 : 52 : + if (ibb_recv_record_update(hash, xid_info, file_no)) 2621 : 0 : + return true; 2622 : : + 2623 : 52 : + return false; 2624 : : +} 2625 : : + 2626 : : + 2627 : : +static bool 2628 : 40 : +ibb_recv_record_complete(HASH *hash, uint64_t file_no, 2629 : : + const byte *rec_data, int data_len) 2630 : : +{ 2631 : 40 : + const byte *p= rec_data; 2632 : : + 2633 : 40 : + byte subtype= *p++; 2634 : 40 : + bool is_commit= (subtype & IBB_FL_XA_TYPE_MASK) == IBB_FL_XA_TYPE_COMMIT; 2635 : 40 : + long formatID= uint4korr(p); 2636 : 40 : + p+= 4; 2637 : 40 : + byte gtrid_length= *p++; 2638 : 40 : + byte bqual_length= *p++; 2639 : 40 : + if (UNIV_UNLIKELY(gtrid_length > 64) || 2640 : 40 : + UNIV_UNLIKELY(bqual_length > 64)) 2641 : : + { 2642 : 0 : + sql_print_error("InnoDB: Corrupt %s record found in file_no=%" PRIu64 2643 : : + ", invalid XID lengths %u/%u", 2644 : : + (is_commit ? "commit" : "rollback"), file_no, 2645 : : + (uint)gtrid_length, (uint)bqual_length); 2646 : 0 : + return true; 2647 : : + } 2648 : 40 : + const char *xid_data= reinterpret_cast(p); 2649 : 40 : + p+= gtrid_length + bqual_length; 2650 : 40 : + if ((int)(p - rec_data) > data_len) 2651 : : + { 2652 : 0 : + sql_print_error("InnoDB: Corrupt prepare record found in file_no=%" PRIu64 2653 : : + ", only %d bytes but expected %u", file_no, 2654 : 0 : + data_len, (uint)(p - rec_data)); 2655 : 0 : + return true; 2656 : : + } 2657 : 40 : + handler_binlog_xid_info::binlog_xid_state xid_state= is_commit ? 2658 : : + handler_binlog_xid_info::BINLOG_COMMIT : 2659 : : + handler_binlog_xid_info::BINLOG_ROLLBACK; 2660 : 40 : + ibb_binlog_xid_info *xid_info= new ibb_binlog_xid_info(xid_state, file_no); 2661 : 40 : + if (!xid_info) 2662 : : + { 2663 : 0 : + sql_print_error("InnoDB: Out of memory while scanning file_no=%" PRIu64, 2664 : : + file_no); 2665 : 0 : + return true; 2666 : : + } 2667 : 40 : + xid_info->xid.set(formatID, xid_data, gtrid_length, 2668 : 40 : + xid_data + bqual_length, bqual_length); 2669 : 40 : + if (ibb_recv_record_update(hash, xid_info, file_no)) 2670 : 0 : + return true; 2671 : 40 : + return false; 2672 : : +} 2673 : : + 2674 : : + 2675 : : +static bool 2676 : 47 : +binlog_scan_for_xid(uint64_t start_file_no, uint64_t start_offset, 2677 : : + HASH *hash) 2678 : : +{ 2679 : 47 : + if (start_file_no == ~(uint64_t)0) 2680 : 0 : + return false; // No active XID, no scan needed. 2681 : 47 : + binlog_chunk_reader chunk_reader(binlog_cur_end_offset); 2682 : : + std::unique_ptr 2683 : 94 : + page_buf(static_cast(ut_malloc(ibb_page_size, mem_key_binlog)), 2684 : 141 : + [](byte *p) {ut_free(p);}); 2685 : 47 : + if (page_buf == nullptr) 2686 : 0 : + return true; 2687 : 47 : + chunk_reader.set_page_buf(page_buf.get()); 2688 : 47 : + chunk_reader.seek(start_file_no, start_offset); 2689 : 47 : + chunk_reader.skip_partial(true); 2690 : : + 2691 : : + byte buf[1024]; 2692 : : + static_assert(sizeof(buf) >= ibb_prepare_record_max_size, 2693 : : + "Need space for max size prepare record"); 2694 : : + for (;;) 2695 : : + { 2696 : 1507 : + int res= chunk_reader.read_data(buf, sizeof(buf), true); 2697 : 1507 : + if (res < 0) 2698 : : + { 2699 : 2 : + sql_print_error("InnoDB: Error reading binlog while recovering XIDs of " 2700 : : + "possibly prepared transactions. Recovery will be " 2701 : : + "incomplete."); 2702 : 2 : + break; 2703 : : + } 2704 : 1505 : + if (res == 0) 2705 : : + { 2706 : : + /* EOF, so scan is done. */ 2707 : 45 : + return false; 2708 : : + } 2709 : 1460 : + if (chunk_reader.cur_type() == FSP_BINLOG_TYPE_XA_PREPARE) 2710 : : + { 2711 : 52 : + if (ibb_recv_record_prepare(hash, chunk_reader.s.rec_start_file_no, 2712 : : + buf, res)) 2713 : 0 : + return true; 2714 : : + } 2715 : 1408 : + else if (chunk_reader.cur_type() == FSP_BINLOG_TYPE_XA_COMPLETE) 2716 : : + { 2717 : 40 : + if (ibb_recv_record_complete(hash, chunk_reader.s.rec_start_file_no, 2718 : : + buf, res)) 2719 : 0 : + return true; 2720 : : + } 2721 : : + else 2722 : : + { 2723 : : + /* Skip any other record type. */ 2724 : 1368 : + chunk_reader.skip_current(); 2725 : : + } 2726 : 1460 : + } 2727 : : + 2728 : 2 : + return false; 2729 : 47 : +} 2730 : : + 2731 : : + 2732 : : +static bool 2733 : 47 : +ibb_init_xid_hash(HASH *hash, LF_PINS *pins) 2734 : : +{ 2735 : : + /* 2736 : : + Populate our internal XID hash from any prepare records found 2737 : : + while scanning the binlogs. 2738 : : + */ 2739 : 111 : + for (uint32 i= 0; i < hash->records; ++i) 2740 : : + { 2741 : : + const ibb_binlog_xid_info *info= (const ibb_binlog_xid_info *) 2742 : 64 : + my_hash_element(hash, i); 2743 : 64 : + if (info->xid_state != handler_binlog_xid_info::BINLOG_PREPARE) 2744 : 36 : + continue; 2745 : : + 2746 : 28 : + uint64_t oob_file_no= info->num_oob_nodes > 0 ? 2747 : : + info->first_oob_file_no : info->rec_file_no; 2748 : : + /* 2749 : : + This is just to ensure that we load the file header page into the 2750 : : + ibb_file_hash if not there already. 2751 : : + */ 2752 : : + uint64_t dummy; 2753 : 28 : + if (ibb_file_hash.get_oob_ref_file_no(oob_file_no, pins, &dummy)) 2754 : : + { 2755 : 0 : + sql_print_error("InnoDB: Could not process file number %" PRIu64 2756 : : + " while recovering pending XID from existing binlogs, " 2757 : : + "out of memory or unable to read file", oob_file_no); 2758 : 0 : + return true; 2759 : : + } 2760 : : + 2761 : 28 : + if (ibb_xa_xid_hash->add_xid(&info->xid, oob_file_no, pins, 2762 : 28 : + info->num_oob_nodes, 2763 : 28 : + info->first_oob_file_no, 2764 : 28 : + info->first_oob_offset, 2765 : 28 : + info->last_oob_file_no, 2766 : 28 : + info->last_oob_offset)) 2767 : : + { 2768 : 0 : + fprintf(stderr, "InnoDB: Out of memory while recovering pending " 2769 : : + "XID from existing binlogs"); 2770 : 0 : + return true; 2771 : : + } 2772 : : + } 2773 : 47 : + return false; 2774 : : +} 2775 : : + 2776 : : + 2777 : : +/** Allocate a context for out-of-band binlogging. */ 2778 : : +static binlog_oob_context * 2779 : 2614 : +alloc_oob_context(uint32 list_length= 10) 2780 : : +{ 2781 : 2614 : + size_t needed= sizeof(binlog_oob_context) + 2782 : 2614 : + list_length * sizeof(binlog_oob_context::node_info); 2783 : : + binlog_oob_context *c= 2784 : 5228 : + static_cast(ut_malloc(needed, mem_key_binlog)); 2785 : 2614 : + if (c) 2786 : : + { 2787 : 2614 : + if (!(c->lf_pins= lf_hash_get_pins(&ibb_file_hash.hash))) 2788 : : + { 2789 : 0 : + my_error(ER_OUT_OF_RESOURCES, MYF(0)); 2790 : 0 : + ut_free(c); 2791 : 0 : + return nullptr; 2792 : : + } 2793 : 2614 : + c->stmt_start_point= nullptr; 2794 : 2614 : + c->savepoint_stack= nullptr; 2795 : 2614 : + c->pending_file_no= ~(uint64_t)0; 2796 : 2614 : + c->node_list_alloc_len= list_length; 2797 : 2614 : + c->node_list_len= 0; 2798 : 2614 : + c->secondary_ctx= nullptr; 2799 : 2614 : + c->pending_refcount= false; 2800 : 2614 : + c->is_xa_prepared= false; 2801 : : + } 2802 : : + else 2803 : 0 : + my_error(ER_OUTOFMEMORY, MYF(0), needed); 2804 : : + 2805 : 2614 : + return c; 2806 : : +} 2807 : : + 2808 : : + 2809 : : +static void 2810 : 138978 : +innodb_binlog_write_cache(IO_CACHE *cache, 2811 : : + handler_binlog_event_group_info *binlog_info, mtr_t *mtr) 2812 : : +{ 2813 : 138978 : + binlog_oob_context *c= 2814 : : + static_cast(binlog_info->engine_ptr); 2815 : 138978 : + if (!c) 2816 : 2415 : + binlog_info->engine_ptr= c= alloc_oob_context(); 2817 : 138978 : + ut_a(c); 2818 : : + 2819 : 138978 : + if (unlikely(binlog_info->xa_xid)) 2820 : : + { 2821 : : + /* 2822 : : + Write an XID commit record just before the main commit record. 2823 : : + The XID commit record just contains the XID, and is used by binlog XA 2824 : : + crash recovery to ensure than the other storage engine(s) that are part 2825 : : + of the transaciton commit or rollback consistently with the binlog 2826 : : + engine. 2827 : : + */ 2828 : 68 : + chunk_data_xa_complete chunk_data2(binlog_info->xa_xid, true); 2829 : 68 : + fsp_binlog_write_rec(&chunk_data2, mtr, FSP_BINLOG_TYPE_XA_COMPLETE, 2830 : : + c->lf_pins); 2831 : 68 : + } 2832 : : + 2833 : 138978 : + chunk_data_cache chunk_data(cache, binlog_info); 2834 : : + 2835 : 138978 : + fsp_binlog_write_rec(&chunk_data, mtr, FSP_BINLOG_TYPE_COMMIT, c->lf_pins); 2836 : 138978 : + chunk_data.after_copy_data(); 2837 : : + 2838 : 138978 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 2839 : 138978 : + c->pending_file_no= file_no; 2840 : 138978 : + c->pending_offset= 2841 : 138978 : + binlog_cur_end_offset[file_no & 3].load(std::memory_order_relaxed); 2842 : 138978 : +} 2843 : : + 2844 : : + 2845 : : +static inline void 2846 : 320688 : +reset_oob_context(binlog_oob_context *c) 2847 : : +{ 2848 : 320688 : + if (c->stmt_start_point) 2849 : 7042 : + c->stmt_start_point->node_list_len= 0; 2850 : 321250 : + while (c->savepoint_stack != nullptr) 2851 : : + { 2852 : 562 : + binlog_oob_context::savepoint *next_savepoint= c->savepoint_stack->next; 2853 : 562 : + ut_free(c->savepoint_stack); 2854 : 562 : + c->savepoint_stack= next_savepoint; 2855 : : + } 2856 : 320688 : + c->pending_file_no= ~(uint64_t)0; 2857 : 320688 : + if (c->pending_refcount) 2858 : : + { 2859 : 567 : + ibb_file_hash.oob_ref_dec(c->first_node_file_no, c->lf_pins); 2860 : 567 : + c->pending_refcount= false; 2861 : : + } 2862 : 320688 : + c->node_list_len= 0; 2863 : 320688 : + c->secondary_ctx= nullptr; 2864 : 320688 : + c->is_xa_prepared= false; 2865 : 320688 : +} 2866 : : + 2867 : : + 2868 : : +static inline void 2869 : 2595 : +free_oob_context(binlog_oob_context *c) 2870 : : +{ 2871 : 2595 : + ut_ad(!c->pending_refcount /* Should not have pending until free */); 2872 : 2595 : + reset_oob_context(c); /* Defensive programming, should be redundant */ 2873 : 2595 : + ut_free(c->stmt_start_point); 2874 : 2596 : + lf_hash_put_pins(c->lf_pins); 2875 : 2596 : + ut_free(c); 2876 : 2595 : +} 2877 : : + 2878 : : + 2879 : : +static binlog_oob_context * 2880 : 8569 : +ensure_oob_context(void **engine_data, uint32_t needed_len) 2881 : : +{ 2882 : 8569 : + binlog_oob_context *c= static_cast(*engine_data); 2883 : 8569 : + if (c->node_list_alloc_len >= needed_len) 2884 : 8567 : + return c; 2885 : 2 : + if (needed_len < c->node_list_alloc_len + 10) 2886 : 2 : + needed_len= c->node_list_alloc_len + 10; 2887 : 2 : + binlog_oob_context *new_c= alloc_oob_context(needed_len); 2888 : 2 : + if (UNIV_UNLIKELY(!new_c)) 2889 : 0 : + return nullptr; 2890 : 2 : + ut_ad(c->node_list_len <= c->node_list_alloc_len); 2891 : 2 : + memcpy(new_c, c, sizeof(binlog_oob_context) + 2892 : 2 : + c->node_list_len*sizeof(binlog_oob_context::node_info)); 2893 : 2 : + new_c->node_list_alloc_len= needed_len; 2894 : 2 : + *engine_data= new_c; 2895 : 2 : + ut_free(c); 2896 : 2 : + return new_c; 2897 : : +} 2898 : : + 2899 : : + 2900 : : +/** 2901 : : + Binlog an out-of-band piece of event group data. 2902 : : + 2903 : : + For large transactions, we binlog the data in pieces spread out over the 2904 : : + binlog file(s), to avoid a large stall to write large amounts of data during 2905 : : + transaction commit, and to avoid having to keep all of the transaction in 2906 : : + memory or spill it to temporary file. 2907 : : + 2908 : : + The chunks of data are written out in a binary tree structure, to allow 2909 : : + efficiently reading the transaction back in order from start to end. Note 2910 : : + that the binlog is written append-only, so we cannot simply link each chunk 2911 : : + to the following chunk, as the following chunk is unknown when binlogging the 2912 : : + prior chunk. With a binary tree structure, the reader can do a post-order 2913 : : + traversal and only need to keep log_2(N) node pointers in-memory at any time. 2914 : : + 2915 : : + A perfect binary tree of height h has 2**h - 1 nodes. At any time during a 2916 : : + transaction, the out-of-band data in the binary log for that transaction 2917 : : + consists of a forest (eg. a list) of perfect binary trees of strictly 2918 : : + decreasing height, except that the last two trees may have the same height. 2919 : : + For example, here is how it looks for a transaction where 13 nodes (0-12) 2920 : : + have been binlogged out-of-band so far: 2921 : : + 2922 : : + 6 2923 : : + _ / \_ 2924 : : + 2 5 9 12 2925 : : + / \ / \ / \ / \ 2926 : : + 0 1 3 4 7 8 10 11 2927 : : + 2928 : : + In addition to the shown binary tree parent->child pointers, each leaf has a 2929 : : + (single) link to the root node of the prior (at the time the leaf was added) 2930 : : + tree. In the example this means the following links: 2931 : : + 11->10, 10->9, 8->7, 7->6, 4->3, 3->2, 1->0 2932 : : + This allows to fully traverse the forest of perfect binary trees starting 2933 : : + from the last node (12 in the example). In the example, only 10->9 and 7->6 2934 : : + will be needed, but the other links would be needed if the tree had been 2935 : : + completed at earlier stages. 2936 : : + 2937 : : + As a new node is added, there are two different cases on how to maintain 2938 : : + the binary tree forest structure: 2939 : : + 2940 : : + 1. If the last two trees in the forest have the same height h, then those 2941 : : + two trees are replaced by a single tree of height (h+1) with the new 2942 : : + node as root and the two trees as left and right child. The number of 2943 : : + trees in the forest thus decrease by one. 2944 : : + 2945 : : + 2. Otherwise the new node is added at the end of the forest as a tree of 2946 : : + height 1; in this case the forest increases by one tree. 2947 : : + 2948 : : + In both cases, we maintain the invariants that the forest consist of a list 2949 : : + of perfect binary trees, and that the heights of the trees are strictly 2950 : : + decreasing except that the last two trees can have the same height. 2951 : : + 2952 : : + When a transaction is committed, the commit record contains a pointer to 2953 : : + the root node of the last tree in the forest. If the transaction is never 2954 : : + committed (explicitly rolled back or lost due to disconnect or server 2955 : : + restart or crash), then the out-of-band data is simply left in place; it 2956 : : + will be ignored by readers and eventually discarded as the old binlog files 2957 : : + are purged. 2958 : : +*/ 2959 : : +bool 2960 : 17295 : +innodb_binlog_oob_ordered(THD *thd, const unsigned char *data, size_t data_len, 2961 : : + void **engine_data, void **stm_start_data, 2962 : : + void **savepoint_data) 2963 : : +{ 2964 : 17295 : + binlog_oob_context *c= static_cast(*engine_data); 2965 : 17295 : + if (!c) 2966 : 189 : + *engine_data= c= alloc_oob_context(); 2967 : 17295 : + if (UNIV_UNLIKELY(!c)) 2968 : 0 : + return true; 2969 : 17295 : + if (UNIV_UNLIKELY(c->is_xa_prepared)) 2970 : : + { 2971 : 0 : + my_error(ER_XAER_RMFAIL, MYF(0), "IDLE"); 2972 : 0 : + return true; 2973 : : + } 2974 : : + 2975 : 17295 : + if (stm_start_data) 2976 : : + { 2977 : 1993 : + if (c->create_stmt_start_point()) 2978 : 0 : + return true; 2979 : 1993 : + *stm_start_data= nullptr; /* We do not need to store any data there. */ 2980 : 1993 : + if (data_len == 0 && !savepoint_data) 2981 : 0 : + return false; 2982 : : + } 2983 : 17295 : + if (savepoint_data) 2984 : : + { 2985 : 726 : + binlog_oob_context::savepoint *sv= c->create_savepoint(); 2986 : 726 : + if (!sv) 2987 : 0 : + return true; 2988 : 726 : + *((binlog_oob_context::savepoint **)savepoint_data)= sv; 2989 : 726 : + if (data_len == 0) 2990 : 0 : + return false; 2991 : : + } 2992 : 17295 : + ut_ad(data_len > 0); 2993 : : + 2994 : 17295 : + mtr_t mtr{thd_to_trx(thd)}; 2995 : 17295 : + uint32_t i= c->node_list_len; 2996 : 17295 : + uint64_t new_idx= i==0 ? 0 : c->node_list[i-1].node_index + 1; 2997 : 17295 : + if (i >= 2 && c->node_list[i-2].height == c->node_list[i-1].height) 2998 : : + { 2999 : : + /* Case 1: Replace two trees with a tree rooted in a new node. */ 3000 : : + binlog_oob_context::chunk_data_oob oob_data 3001 : : + (new_idx, 3002 : 7437 : + c->node_list[i-2].file_no, c->node_list[i-2].offset, 3003 : 7437 : + c->node_list[i-1].file_no, c->node_list[i-1].offset, 3004 : 7437 : + static_cast(data), data_len); 3005 : 7437 : + if (c->binlog_node(i-2, new_idx, i-2, i-1, &oob_data, c->lf_pins, &mtr)) 3006 : 0 : + return true; 3007 : 7437 : + c->node_list_len= i - 1; 3008 : 14874 : + } 3009 : 9858 : + else if (i > 0) 3010 : : + { 3011 : : + /* Case 2: Add the new node as a singleton tree. */ 3012 : 8569 : + c= ensure_oob_context(engine_data, i+1); 3013 : 8569 : + if (!c) 3014 : 0 : + return true; 3015 : : + binlog_oob_context::chunk_data_oob oob_data 3016 : : + (new_idx, 3017 : : + 0, 0, /* NULL left child signifies a leaf */ 3018 : 8569 : + c->node_list[i-1].file_no, c->node_list[i-1].offset, 3019 : 8569 : + static_cast(data), data_len); 3020 : 8569 : + if (c->binlog_node(i, new_idx, i-1, i-1, &oob_data, c->lf_pins, &mtr)) 3021 : 0 : + return true; 3022 : 8569 : + c->node_list_len= i + 1; 3023 : 8569 : + } 3024 : : + else 3025 : : + { 3026 : : + /* Special case i==0, like case 2 but no prior node to link to. */ 3027 : : + binlog_oob_context::chunk_data_oob oob_data 3028 : 1289 : + (new_idx, 0, 0, 0, 0, static_cast(data), data_len); 3029 : : + /* 3030 : : + Note that we must increment the refcount _before_ binlogging the 3031 : : + record. Because if the record ends up spanning two binlog files, the 3032 : : + new binlog file must have oob reference back to the start of the OOB 3033 : : + record, not to the end of it! 3034 : : + 3035 : : + We do not need any locking around getting the active file_no here; even 3036 : : + if active would move we would just have a slightly conservative oob 3037 : : + reference in the file header. (Though at this point the server layer 3038 : : + is holding a lock anyway that prevents other binlogging to happen 3039 : : + concurrently). 3040 : : + */ 3041 : 1289 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 3042 : 1289 : + c->pending_refcount= 3043 : 1289 : + ibb_file_hash.oob_ref_inc(active, c->lf_pins) != ~(uint64_t)0; 3044 : : + 3045 : 1289 : + if (c->binlog_node(i, new_idx, ~(uint32_t)0, ~(uint32_t)0, &oob_data, 3046 : : + c->lf_pins, &mtr)) 3047 : 0 : + return true; 3048 : : + 3049 : : + /* 3050 : : + Here we could check c->node_list[i].file_no and see if it differs from 3051 : : + the active before we did the binlogging; and if so increment the right 3052 : : + one and decrement the incorrect one. But it does not seem worthwhile, as 3053 : : + this is unlikely/impossible, and it just causes a slightly more 3054 : : + conservative OOB reference protection from purge anyway. 3055 : : + */ 3056 : 1289 : + c->first_node_file_no= c->node_list[i].file_no; 3057 : 1289 : + c->first_node_offset= c->node_list[i].offset; 3058 : 1289 : + c->node_list_len= 1; 3059 : 1289 : + } 3060 : : + 3061 : 17295 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 3062 : 17295 : + c->pending_file_no= file_no; 3063 : 17295 : + c->pending_offset= 3064 : 17295 : + binlog_cur_end_offset[file_no & 3].load(std::memory_order_relaxed); 3065 : 17295 : + innodb_binlog_post_commit(&mtr, c); 3066 : 17295 : + return false; 3067 : 17295 : +} 3068 : : + 3069 : : + 3070 : : +bool 3071 : 17295 : +innodb_binlog_oob(THD *thd, const unsigned char *data, size_t data_len, 3072 : : + void **engine_data) 3073 : : +{ 3074 : 17295 : + binlog_oob_context *c= static_cast(*engine_data); 3075 : 17295 : + if (UNIV_LIKELY(c != nullptr)) 3076 : 17295 : + ibb_pending_lsn_fifo.record_commit(c); 3077 : 17295 : + return false; 3078 : : +} 3079 : : + 3080 : : + 3081 : : +/** 3082 : : + Binlog a new out-of-band tree node and put it at position `node` in the list 3083 : : + of trees. A leaf node is denoted by left and right child being identical (and 3084 : : + in this case they point to the root of the prior tree). 3085 : : +*/ 3086 : : +bool 3087 : 17295 : +binlog_oob_context::binlog_node(uint32_t node, uint64_t new_idx, 3088 : : + uint32_t left_node, uint32_t right_node, 3089 : : + chunk_data_oob *oob_data, LF_PINS *pins, 3090 : : + mtr_t *mtr) 3091 : : +{ 3092 : 17295 : + uint32_t new_height= 3093 : 17295 : + left_node == right_node ? 1 : 1 + node_list[left_node].height; 3094 : 17295 : + mtr->start(); 3095 : : + std::pair new_file_no_offset= 3096 : 17295 : + fsp_binlog_write_rec(oob_data, mtr, FSP_BINLOG_TYPE_OOB_DATA, pins); 3097 : 17295 : + mtr->commit(); 3098 : 17295 : + node_list[node].file_no= new_file_no_offset.first; 3099 : 17295 : + node_list[node].offset= new_file_no_offset.second; 3100 : 17295 : + node_list[node].node_index= new_idx; 3101 : 17295 : + node_list[node].height= new_height; 3102 : 17295 : + return false; 3103 : : +} 3104 : : + 3105 : : + 3106 : 17295 : +binlog_oob_context::chunk_data_oob::chunk_data_oob(uint64_t idx, 3107 : : + uint64_t left_file_no, uint64_t left_offset, 3108 : : + uint64_t right_file_no, uint64_t right_offset, 3109 : 17295 : + const byte *data, size_t data_len) 3110 : 17295 : + : sofar(0), main_len(data_len), main_data(data) 3111 : : +{ 3112 : 17295 : + ut_ad(data_len > 0); 3113 : 17295 : + byte *p= &header_buf[0]; 3114 : 17295 : + p= compr_int_write(p, idx); 3115 : 17295 : + p= compr_int_write(p, left_file_no); 3116 : 17295 : + p= compr_int_write(p, left_offset); 3117 : 17295 : + p= compr_int_write(p, right_file_no); 3118 : 17295 : + p= compr_int_write(p, right_offset); 3119 : 17295 : + ut_ad((uint32_t)(p - &header_buf[0]) <= max_buffer); 3120 : 17295 : + header_len= (uint32_t)(p - &header_buf[0]); 3121 : 17295 : +} 3122 : : + 3123 : : + 3124 : : +std::pair 3125 : 28169 : +binlog_oob_context::chunk_data_oob::copy_data(byte *p, uint32_t max_len) 3126 : : +{ 3127 : 28169 : + uint32_t size= 0; 3128 : : + /* First write header data, if any left. */ 3129 : 28169 : + if (sofar < header_len) 3130 : : + { 3131 : 17301 : + size= std::min(header_len - (uint32_t)sofar, max_len); 3132 : 17301 : + memcpy(p, header_buf + sofar, size); 3133 : 17301 : + p+= size; 3134 : 17301 : + sofar+= size; 3135 : 17301 : + if (UNIV_UNLIKELY(max_len == size)) 3136 : 6 : + return {size, sofar == header_len + main_len}; 3137 : 17295 : + max_len-= size; 3138 : : + } 3139 : : + 3140 : : + /* Then write the main chunk data. */ 3141 : 28163 : + ut_ad(sofar >= header_len); 3142 : 28163 : + ut_ad(main_len > 0); 3143 : : + uint32_t size2= 3144 : 28163 : + (uint32_t)std::min(header_len + main_len - sofar, (uint64_t)max_len); 3145 : 28163 : + memcpy(p, main_data + (sofar - header_len), size2); 3146 : 28163 : + sofar+= size2; 3147 : 28163 : + return {size + size2, sofar == header_len + main_len}; 3148 : : +} 3149 : : + 3150 : : + 3151 : : +bool 3152 : 1993 : +binlog_oob_context::create_stmt_start_point() 3153 : : +{ 3154 : 1993 : + if (!stmt_start_point || node_list_len > stmt_start_point->alloc_len) 3155 : : + { 3156 : 317 : + ut_free(stmt_start_point); 3157 : 317 : + size_t size= sizeof(savepoint) + node_list_len * sizeof(node_info); 3158 : 317 : + stmt_start_point= 3159 : 634 : + static_cast(ut_malloc(size, mem_key_binlog)); 3160 : 317 : + if (!stmt_start_point) 3161 : : + { 3162 : 0 : + my_error(ER_OUTOFMEMORY, MYF(0), size); 3163 : 0 : + return true; 3164 : : + } 3165 : 317 : + stmt_start_point->alloc_len= node_list_len; 3166 : : + } 3167 : 1993 : + stmt_start_point->node_list_len= node_list_len; 3168 : 1993 : + memcpy(stmt_start_point->node_list, node_list, 3169 : 1993 : + node_list_len * sizeof(node_info)); 3170 : 1993 : + return false; 3171 : : +} 3172 : : + 3173 : : + 3174 : : +binlog_oob_context::savepoint * 3175 : 726 : +binlog_oob_context::create_savepoint() 3176 : : +{ 3177 : 726 : + size_t size= sizeof(savepoint) + node_list_len * sizeof(node_info); 3178 : 1452 : + savepoint *s= static_cast(ut_malloc(size, mem_key_binlog)); 3179 : 726 : + if (!s) 3180 : : + { 3181 : 0 : + my_error(ER_OUTOFMEMORY, MYF(0), size); 3182 : 0 : + return nullptr; 3183 : : + } 3184 : 726 : + s->next= savepoint_stack; 3185 : 726 : + s->node_list_len= node_list_len; 3186 : 726 : + memcpy(s->node_list, node_list, node_list_len * sizeof(node_info)); 3187 : 726 : + savepoint_stack= s; 3188 : 726 : + return s; 3189 : : +} 3190 : : + 3191 : : + 3192 : : +void 3193 : 668 : +binlog_oob_context::rollback_to_savepoint(savepoint *savepoint) 3194 : : +{ 3195 : 668 : + ut_a(node_list_alloc_len >= savepoint->node_list_len); 3196 : 668 : + node_list_len= savepoint->node_list_len; 3197 : 668 : + memcpy(node_list, savepoint->node_list, 3198 : 668 : + savepoint->node_list_len * sizeof(node_info)); 3199 : : + 3200 : : + /* Remove any later savepoints from the stack. */ 3201 : : + for (;;) 3202 : : + { 3203 : 832 : + struct savepoint *s= savepoint_stack; 3204 : 832 : + ut_ad(s != nullptr /* Should always find the savepoint on the stack. */); 3205 : 832 : + if (UNIV_UNLIKELY(!s)) 3206 : 0 : + break; 3207 : 832 : + if (s == savepoint) 3208 : 668 : + break; 3209 : 164 : + savepoint_stack= s->next; 3210 : 164 : + ut_free(s); 3211 : 164 : + } 3212 : 668 : +} 3213 : : + 3214 : : + 3215 : : +void 3216 : 7 : +binlog_oob_context::rollback_to_stmt_start() 3217 : : +{ 3218 : 7 : + ut_a(node_list_alloc_len >= stmt_start_point->node_list_len); 3219 : 7 : + node_list_len= stmt_start_point->node_list_len; 3220 : 7 : + memcpy(node_list, stmt_start_point->node_list, 3221 : 7 : + stmt_start_point->node_list_len * sizeof(node_info)); 3222 : 7 : +} 3223 : : + 3224 : : + 3225 : : +void 3226 : 675 : +ibb_savepoint_rollback(THD *thd, void **engine_data, 3227 : : + void **stmt_start_data, void **savepoint_data) 3228 : : +{ 3229 : 675 : + binlog_oob_context *c= static_cast(*engine_data); 3230 : 675 : + ut_a(c != nullptr); 3231 : : + 3232 : 675 : + if (stmt_start_data) 3233 : : + { 3234 : 7 : + ut_ad(savepoint_data == nullptr); 3235 : 7 : + c->rollback_to_stmt_start(); 3236 : : + } 3237 : : + 3238 : 675 : + if (savepoint_data) 3239 : : + { 3240 : 668 : + ut_ad(stmt_start_data == nullptr); 3241 : 668 : + binlog_oob_context::savepoint *savepoint= 3242 : : + (binlog_oob_context::savepoint *)*savepoint_data; 3243 : 668 : + c->rollback_to_savepoint(savepoint); 3244 : : + } 3245 : 675 : +} 3246 : : + 3247 : : + 3248 : : +void 3249 : 318093 : +innodb_reset_oob(void **engine_data) 3250 : : +{ 3251 : 318093 : + binlog_oob_context *c= static_cast(*engine_data); 3252 : 318093 : + if (c) 3253 : 318093 : + reset_oob_context(c); 3254 : 318093 : +} 3255 : : + 3256 : : + 3257 : : +void 3258 : 2596 : +innodb_free_oob(void *engine_data) 3259 : : +{ 3260 : 2596 : + free_oob_context(static_cast(engine_data)); 3261 : 2595 : +} 3262 : : + 3263 : : + 3264 : 2204 : +innodb_binlog_oob_reader::innodb_binlog_oob_reader() 3265 : : +{ 3266 : : + /* Nothing. */ 3267 : 2204 : +} 3268 : : + 3269 : : + 3270 : 2202 : +innodb_binlog_oob_reader::~innodb_binlog_oob_reader() 3271 : : +{ 3272 : : + /* Nothing. */ 3273 : 2202 : +} 3274 : : + 3275 : : + 3276 : : +void 3277 : 24674 : +innodb_binlog_oob_reader::push_state(enum oob_states state, uint64_t file_no, 3278 : : + uint64_t offset, bool is_leftmost) 3279 : : +{ 3280 : : + stack_entry new_entry; 3281 : 24674 : + new_entry.state= state; 3282 : 24674 : + new_entry.file_no= file_no; 3283 : 24674 : + new_entry.offset= offset; 3284 : 24674 : + new_entry.is_leftmost= is_leftmost; 3285 : 24674 : + stack.emplace_back(std::move(new_entry)); 3286 : 24674 : +} 3287 : : + 3288 : : + 3289 : : +void 3290 : 590 : +innodb_binlog_oob_reader::start_traversal(uint64_t file_no, uint64_t offset) 3291 : : +{ 3292 : 590 : + stack.clear(); 3293 : 590 : + push_state(ST_initial, file_no, offset, true); 3294 : 590 : +} 3295 : : + 3296 : : + 3297 : : +/** 3298 : : + Read from out-of-band event group data. 3299 : : + 3300 : : + Does a state-machine incremental traversal of the forest of perfect binary 3301 : : + trees of oob records in the event group. May read just the data available 3302 : : + on one page, thus returning less than the requested number of bytes (this 3303 : : + is to prefer to inspect each page only once, returning data page-by-page as 3304 : : + long as reader asks for at least a full page worth of data). 3305 : : +*/ 3306 : : +int 3307 : 33339 : +innodb_binlog_oob_reader::read_data(binlog_chunk_reader *chunk_rd, 3308 : : + uchar *buf, int len) 3309 : : +{ 3310 : : + stack_entry *e; 3311 : : + uint64_t chunk_idx; 3312 : : + uint64_t left_file_no; 3313 : : + uint64_t left_offset; 3314 : : + int res; 3315 : : + const uchar *p_end; 3316 : : + const uchar *p; 3317 : 33339 : + std::pair v_and_p; 3318 : : + int size; 3319 : : + 3320 : 33339 : + if (stack.empty()) 3321 : : + { 3322 : 0 : + ut_ad(0 /* Should not call when no more oob data to read. */); 3323 : 0 : + return 0; 3324 : : + } 3325 : : + 3326 : 82089 : +again: 3327 : 82089 : + e= &(stack[stack.size() - 1]); 3328 : 82089 : + switch (e->state) 3329 : : + { 3330 : 24666 : + case ST_initial: 3331 : 24666 : + chunk_rd->seek(e->file_no, e->offset); 3332 : : + static_assert(sizeof(e->rd_buf) == 5*COMPR_INT_MAX64, 3333 : : + "rd_buf size must match code using it"); 3334 : 24666 : + res= chunk_rd->read_data(e->rd_buf, 5*COMPR_INT_MAX64, true); 3335 : 24666 : + if (res < 0) 3336 : 0 : + return -1; 3337 : 24666 : + if (chunk_rd->cur_type() != FSP_BINLOG_TYPE_OOB_DATA) 3338 : 0 : + return chunk_rd->read_error_corruption("Wrong chunk type"); 3339 : 24666 : + if (res == 0) 3340 : 0 : + return chunk_rd->read_error_corruption("Unexpected EOF, expected " 3341 : 0 : + "oob chunk"); 3342 : 24666 : + e->rd_buf_len= res; 3343 : 24666 : + p_end= e->rd_buf + res; 3344 : 24666 : + v_and_p= compr_int_read(e->rd_buf); 3345 : 24666 : + p= v_and_p.second; 3346 : 24666 : + if (p > p_end) 3347 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 3348 : 24666 : + chunk_idx= v_and_p.first; 3349 : : + (void)chunk_idx; 3350 : : + 3351 : 24666 : + v_and_p= compr_int_read(p); 3352 : 24666 : + p= v_and_p.second; 3353 : 24666 : + if (p > p_end) 3354 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 3355 : 24666 : + left_file_no= v_and_p.first; 3356 : 24666 : + v_and_p= compr_int_read(p); 3357 : 24666 : + p= v_and_p.second; 3358 : 24666 : + if (p > p_end) 3359 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 3360 : 24666 : + left_offset= v_and_p.first; 3361 : : + 3362 : 24666 : + v_and_p= compr_int_read(p); 3363 : 24666 : + p= v_and_p.second; 3364 : 24666 : + if (p > p_end) 3365 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 3366 : 24666 : + e->right_file_no= v_and_p.first; 3367 : 24666 : + v_and_p= compr_int_read(p); 3368 : 24666 : + p= v_and_p.second; 3369 : 24666 : + if (p > p_end) 3370 : 0 : + return chunk_rd->read_error_corruption("Short chunk"); 3371 : 24666 : + e->right_offset= v_and_p.first; 3372 : 24666 : + e->rd_buf_sofar= (uint32_t)(p - e->rd_buf); 3373 : 24666 : + if (left_file_no == 0 && left_offset == 0) 3374 : : + { 3375 : : + /* Leaf node. */ 3376 : 13023 : + if (e->is_leftmost && !(e->right_file_no == 0 && e->right_offset == 0)) 3377 : : + { 3378 : : + /* Traverse the prior tree(s) in the forst. */ 3379 : 798 : + e->state= ST_traversing_prior_trees; 3380 : 798 : + chunk_rd->save_pos(&e->saved_pos); 3381 : 798 : + push_state(ST_initial, e->right_file_no, e->right_offset, true); 3382 : : + } 3383 : : + else 3384 : 12225 : + e->state= ST_self; 3385 : : + } 3386 : : + else 3387 : : + { 3388 : 11643 : + e->state= ST_traversing_left_child; 3389 : 11643 : + chunk_rd->save_pos(&e->saved_pos); 3390 : 11643 : + push_state(ST_initial, left_file_no, left_offset, e->is_leftmost); 3391 : : + } 3392 : 24666 : + goto again; 3393 : : + 3394 : 798 : + case ST_traversing_prior_trees: 3395 : 798 : + chunk_rd->restore_pos(&e->saved_pos); 3396 : 798 : + e->state= ST_self; 3397 : 798 : + goto again; 3398 : : + 3399 : 11643 : + case ST_traversing_left_child: 3400 : 11643 : + e->state= ST_traversing_right_child; 3401 : 11643 : + push_state(ST_initial, e->right_file_no, e->right_offset, false); 3402 : 11643 : + goto again; 3403 : : + 3404 : 11643 : + case ST_traversing_right_child: 3405 : 11643 : + chunk_rd->restore_pos(&e->saved_pos); 3406 : 11643 : + e->state= ST_self; 3407 : 11643 : + goto again; 3408 : : + 3409 : 33339 : + case ST_self: 3410 : 33339 : + size= 0; 3411 : 33339 : + if (e->rd_buf_len > e->rd_buf_sofar) 3412 : : + { 3413 : : + /* Use any excess data from when the header was read. */ 3414 : 24666 : + size= std::min((int)(e->rd_buf_len - e->rd_buf_sofar), len); 3415 : 24666 : + memcpy(buf, e->rd_buf + e->rd_buf_sofar, size); 3416 : 24666 : + e->rd_buf_sofar+= size; 3417 : 24666 : + len-= size; 3418 : 24666 : + buf+= size; 3419 : : + } 3420 : : + 3421 : 33339 : + if (UNIV_LIKELY(len > 0) && UNIV_LIKELY(!chunk_rd->end_of_record())) 3422 : : + { 3423 : 33271 : + res= chunk_rd->read_data(buf, len, false); 3424 : 33271 : + if (res < 0) 3425 : 0 : + return -1; 3426 : 33271 : + size+= res; 3427 : : + } 3428 : : + 3429 : 33339 : + if (chunk_rd->end_of_record()) 3430 : : + { 3431 : : + /* This oob record done, pop the state. */ 3432 : 24666 : + ut_ad(!stack.empty()); 3433 : 24666 : + stack.erase(stack.end() - 1, stack.end()); 3434 : : + } 3435 : 33339 : + return size; 3436 : : + 3437 : 0 : + default: 3438 : 0 : + ut_ad(0); 3439 : 0 : + return -1; 3440 : : + } 3441 : : +} 3442 : : + 3443 : : + 3444 : 2204 : +ha_innodb_binlog_reader::ha_innodb_binlog_reader(bool wait_durable, 3445 : : + uint64_t file_no, 3446 : 2204 : + uint64_t offset) 3447 : 2204 : + : chunk_rd(wait_durable ? 3448 : : + binlog_cur_durable_offset : binlog_cur_end_offset), 3449 : 2204 : + requested_file_no(~(uint64_t)0), 3450 : 4408 : + rd_buf_len(0), rd_buf_sofar(0), state(ST_read_next_event_group) 3451 : : +{ 3452 : 4408 : + page_buf= static_cast(ut_malloc(ibb_page_size, mem_key_binlog)); 3453 : 2204 : + chunk_rd.set_page_buf(page_buf); 3454 : 2204 : + if (offset < ibb_page_size) 3455 : 1606 : + offset= ibb_page_size; 3456 : 2204 : + chunk_rd.seek(file_no, offset); 3457 : 2204 : + chunk_rd.skip_partial(true); 3458 : 2204 : +} 3459 : : + 3460 : : + 3461 : 3777 : +ha_innodb_binlog_reader::~ha_innodb_binlog_reader() 3462 : : +{ 3463 : 2202 : + ut_free(page_buf); 3464 : 3777 : +} 3465 : : + 3466 : : + 3467 : : +/** 3468 : : + Read data from current position in binlog. 3469 : : + 3470 : : + If the data is written to disk (visible at the OS level, even if not 3471 : : + necessarily fsync()'ed to disk), we can read directly from the file. 3472 : : + Otherwise, the data must still be available in the buffer pool and 3473 : : + we can read it from there. 3474 : : + 3475 : : + First try a dirty read of current state; if this says the data is available 3476 : : + to read from the file, this is safe to do (data cannot become un-written). 3477 : : + 3478 : : + If not, then check if the page is in the buffer pool; if not, then likewise 3479 : : + we know it's safe to read from the file directly. 3480 : : + 3481 : : + Finally, do another check of the current state. This will catch the case 3482 : : + where we looked for a page in binlog file N, but its tablespace id has been 3483 : : + recycled, so we got a page from (N+2) instead. In this case also, we can 3484 : : + then read from the real file. 3485 : : +*/ 3486 : 51168 : +int ha_innodb_binlog_reader::read_binlog_data(uchar *buf, uint32_t len) 3487 : : +{ 3488 : 51168 : + int res= read_data(buf, len); 3489 : 51168 : + chunk_rd.release(res == 0); 3490 : 51168 : + cur_file_no= chunk_rd.current_file_no(); 3491 : 51168 : + cur_file_pos= chunk_rd.current_pos(); 3492 : 51168 : + return res; 3493 : : +} 3494 : : + 3495 : : + 3496 : 51168 : +int ha_innodb_binlog_reader::read_data(uchar *buf, uint32_t len) 3497 : : +{ 3498 : : + int res; 3499 : : + const uchar *p_end; 3500 : : + const uchar *p; 3501 : 51168 : + std::pair v_and_p; 3502 : 51168 : + int sofar= 0; 3503 : : + 3504 : 350579 : +again: 3505 : 401747 : + switch (state) 3506 : : + { 3507 : 206697 : + case ST_read_next_event_group: 3508 : : + static_assert(sizeof(rd_buf) == 5*COMPR_INT_MAX64, 3509 : : + "rd_buf size must match code using it"); 3510 : 206697 : + res= chunk_rd.read_data(rd_buf, 5*COMPR_INT_MAX64, true); 3511 : 206697 : + if (res < 0) 3512 : 0 : + return res; 3513 : 206697 : + if (res == 0) 3514 : 12768 : + return sofar; 3515 : 193929 : + if (chunk_rd.cur_type() != FSP_BINLOG_TYPE_COMMIT) 3516 : : + { 3517 : 36295 : + chunk_rd.skip_current(); 3518 : 36295 : + goto again; 3519 : : + } 3520 : : + /* Found the start of a commit record. */ 3521 : 157634 : + chunk_rd.skip_partial(false); 3522 : : + 3523 : : + /* Read the header of the commit record to see if there's any oob data. */ 3524 : 157634 : + rd_buf_len= res; 3525 : 157634 : + p_end= rd_buf + res; 3526 : 157634 : + v_and_p= compr_int_read(rd_buf); 3527 : 157634 : + p= v_and_p.second; 3528 : 157634 : + if (p > p_end) 3529 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3530 : 157634 : + oob_count= v_and_p.first; 3531 : 157634 : + oob_count2= 0; 3532 : : + 3533 : 157634 : + if (oob_count > 0) 3534 : : + { 3535 : : + /* Skip the pointer to first chunk. */ 3536 : 574 : + v_and_p= compr_int_read(p); 3537 : 574 : + p= v_and_p.second; 3538 : 574 : + if (p > p_end) 3539 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3540 : 574 : + v_and_p= compr_int_read(p); 3541 : 574 : + p= v_and_p.second; 3542 : 574 : + if (p > p_end) 3543 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3544 : : + 3545 : 574 : + v_and_p= compr_int_read(p); 3546 : 574 : + p= v_and_p.second; 3547 : 574 : + if (p > p_end) 3548 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3549 : 574 : + oob_last_file_no= v_and_p.first; 3550 : 574 : + v_and_p= compr_int_read(p); 3551 : 574 : + p= v_and_p.second; 3552 : 574 : + if (p > p_end) 3553 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3554 : 574 : + oob_last_offset= v_and_p.first; 3555 : : + 3556 : : + /* Check for any secondary oob data. */ 3557 : 574 : + v_and_p= compr_int_read(p); 3558 : 574 : + p= v_and_p.second; 3559 : 574 : + if (p > p_end) 3560 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3561 : 574 : + oob_count2= v_and_p.first; 3562 : : + 3563 : 574 : + if (oob_count2 > 0) 3564 : : + { 3565 : : + /* Skip the pointer to first chunk. */ 3566 : 16 : + v_and_p= compr_int_read(p); 3567 : 16 : + p= v_and_p.second; 3568 : 16 : + if (p > p_end) 3569 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3570 : 16 : + v_and_p= compr_int_read(p); 3571 : 16 : + p= v_and_p.second; 3572 : 16 : + if (p > p_end) 3573 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3574 : : + 3575 : 16 : + v_and_p= compr_int_read(p); 3576 : 16 : + p= v_and_p.second; 3577 : 16 : + if (p > p_end) 3578 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3579 : 16 : + oob_last_file_no2= v_and_p.first; 3580 : 16 : + v_and_p= compr_int_read(p); 3581 : 16 : + p= v_and_p.second; 3582 : 16 : + if (p > p_end) 3583 : 0 : + return chunk_rd.read_error_corruption("Short chunk"); 3584 : 16 : + oob_last_offset2= v_and_p.first; 3585 : : + } 3586 : : + } 3587 : : + 3588 : 157634 : + rd_buf_sofar= (uint32_t)(p - rd_buf); 3589 : 157634 : + state= ST_read_commit_record; 3590 : 157634 : + goto again; 3591 : : + 3592 : 161711 : + case ST_read_commit_record: 3593 : 161711 : + if (rd_buf_len > rd_buf_sofar) 3594 : : + { 3595 : : + /* Use any excess data from when the header was read. */ 3596 : 157634 : + int size= std::min((int)(rd_buf_len - rd_buf_sofar), (int)len); 3597 : 157634 : + memcpy(buf, rd_buf + rd_buf_sofar, size); 3598 : 157634 : + rd_buf_sofar+= size; 3599 : 157634 : + len-= size; 3600 : 157634 : + buf+= size; 3601 : 157634 : + sofar+= size; 3602 : : + } 3603 : : + 3604 : 161711 : + if (UNIV_LIKELY(len > 0) && UNIV_LIKELY(!chunk_rd.end_of_record())) 3605 : : + { 3606 : 161711 : + res= chunk_rd.read_data(buf, len, false); 3607 : 161711 : + if (res < 0) 3608 : 0 : + return -1; 3609 : 161711 : + len-= res; 3610 : 161711 : + buf+= res; 3611 : 161711 : + sofar+= res; 3612 : : + } 3613 : : + 3614 : 161711 : + if (UNIV_LIKELY(rd_buf_sofar == rd_buf_len) && chunk_rd.end_of_record()) 3615 : : + { 3616 : 157226 : + if (oob_count == 0) 3617 : : + { 3618 : 156652 : + state= ST_read_next_event_group; 3619 : 156652 : + if (len > 0 && !chunk_rd.is_end_of_page()) 3620 : : + { 3621 : : + /* 3622 : : + Let us try to read more data from this page. The goal is to read 3623 : : + from each page only once, as long as caller passes in a buffer at 3624 : : + least as big as our page size. Though commit record header that 3625 : : + spans a page boundary or oob records can break this property. 3626 : : + */ 3627 : 156650 : + goto again; 3628 : : + } 3629 : : + } 3630 : : + else 3631 : : + { 3632 : 574 : + oob_reader.start_traversal(oob_last_file_no, oob_last_offset); 3633 : 574 : + chunk_rd.save_pos(&saved_commit_pos); 3634 : 574 : + state= ST_read_oob_data; 3635 : : + } 3636 : 576 : + if (sofar == 0) 3637 : 0 : + goto again; 3638 : : + } 3639 : : + 3640 : 5061 : + return sofar; 3641 : : + 3642 : 33339 : + case ST_read_oob_data: 3643 : 33339 : + res= oob_reader.read_data(&chunk_rd, buf, len); 3644 : 33339 : + if (res < 0) 3645 : 0 : + return -1; 3646 : 33339 : + if (oob_reader.oob_traversal_done()) 3647 : : + { 3648 : 582 : + if (UNIV_UNLIKELY(oob_count2 > 0)) 3649 : : + { 3650 : : + /* Switch over to secondary oob data. */ 3651 : 16 : + oob_count= oob_count2; 3652 : 16 : + oob_count2= 0; 3653 : 16 : + oob_last_file_no= oob_last_file_no2; 3654 : 16 : + oob_last_offset= oob_last_offset2; 3655 : 16 : + oob_reader.start_traversal(oob_last_file_no, oob_last_offset); 3656 : 16 : + state= ST_read_oob_data; 3657 : : + } 3658 : : + else 3659 : : + { 3660 : 566 : + chunk_rd.restore_pos(&saved_commit_pos); 3661 : 566 : + state= ST_read_next_event_group; 3662 : : + } 3663 : : + } 3664 : 33339 : + if (UNIV_UNLIKELY(res == 0)) 3665 : : + { 3666 : 0 : + ut_ad(0 /* Should have had oob_traversal_done() last time then. */); 3667 : 0 : + if (sofar == 0) 3668 : 0 : + goto again; 3669 : : + } 3670 : 33339 : + return sofar + res; 3671 : : + 3672 : 0 : + default: 3673 : 0 : + ut_ad(0); 3674 : 0 : + return -1; 3675 : : + } 3676 : : +} 3677 : : + 3678 : : + 3679 : : +bool 3680 : 28357 : +ha_innodb_binlog_reader::data_available() 3681 : : +{ 3682 : 28357 : + if (state != ST_read_next_event_group) 3683 : 0 : + return true; 3684 : 28357 : + return chunk_rd.data_available(); 3685 : : +} 3686 : : + 3687 : : + 3688 : : +bool 3689 : 11056 : +ha_innodb_binlog_reader::wait_available(THD *thd, 3690 : : + const struct timespec *abstime) 3691 : : +{ 3692 : 11056 : + bool is_timeout= false; 3693 : 11056 : + lsn_t pending_sync_lsn= 0; 3694 : 11056 : + bool did_enter_cond= false; 3695 : : + PSI_stage_info old_stage; 3696 : : + 3697 : 11056 : + if (data_available()) 3698 : 1628 : + return false; 3699 : : + 3700 : 9428 : + mysql_mutex_lock(&binlog_durable_mutex); 3701 : : + for (;;) 3702 : : + { 3703 : : + /* Process anything that has become durable since we last looked. */ 3704 : 21754 : + lsn_t durable_lsn= log_sys.get_flushed_lsn(std::memory_order_relaxed); 3705 : 21754 : + ibb_pending_lsn_fifo.process_durable_lsn(durable_lsn); 3706 : : + 3707 : : + /* Check if there is anything more pending to be made durable. */ 3708 : 21754 : + if (!ibb_pending_lsn_fifo.is_empty()) 3709 : : + { 3710 : 6223 : + pending_lsn_fifo::entry &e= ibb_pending_lsn_fifo.cur_head(); 3711 : 6223 : + if (durable_lsn < e.lsn) 3712 : 6223 : + pending_sync_lsn= e.lsn; 3713 : : + } 3714 : : + 3715 : : + /* 3716 : : + Check if there is data available for us now. 3717 : : + As we are holding binlog_durable_mutex, active_binlog_file_no cannot 3718 : : + move during this check. 3719 : : + */ 3720 : 21754 : + uint64_t cur= active_binlog_file_no.load(std::memory_order_relaxed); 3721 : : + uint64_t durable_offset= 3722 : 21754 : + binlog_cur_durable_offset[cur & 3].load(std::memory_order_relaxed); 3723 : 21754 : + if (durable_offset == 0 && chunk_rd.s.file_no + 1 == cur) 3724 : : + { 3725 : : + /* 3726 : : + If active has durable position=0, it means the current durable 3727 : : + position is somewhere in active-1. 3728 : : + */ 3729 : 462 : + cur= chunk_rd.s.file_no; 3730 : : + durable_offset= 3731 : 924 : + binlog_cur_durable_offset[cur & 3].load(std::memory_order_relaxed); 3732 : : + } 3733 : 21754 : + if (chunk_rd.is_before_pos(cur, durable_offset)) 3734 : 8758 : + break; 3735 : : + 3736 : 12996 : + if (pending_sync_lsn != 0 && ibb_pending_lsn_fifo.flushing_lsn == 0) 3737 : : + { 3738 : : + /* 3739 : : + There is no data available for us now, but there is data that will be 3740 : : + available when the InnoDB redo log has been durably flushed to disk. 3741 : : + So now we will do such a sync (unless another thread is already doing 3742 : : + it), so we can proceed getting more data out. 3743 : : + */ 3744 : 5447 : + ibb_pending_lsn_fifo.flushing_lsn= pending_sync_lsn; 3745 : 5447 : + mysql_mutex_unlock(&binlog_durable_mutex); 3746 : 5447 : + log_write_up_to(pending_sync_lsn, true); 3747 : 5447 : + mysql_mutex_lock(&binlog_durable_mutex); 3748 : 5447 : + ibb_pending_lsn_fifo.flushing_lsn= pending_sync_lsn= 0; 3749 : : + /* Need to loop back to repeat all checks, after releasing the mutex. */ 3750 : 5447 : + continue; 3751 : : + } 3752 : : + 3753 : 7549 : + if (thd && thd_kill_level(thd)) 3754 : 605 : + break; 3755 : : + 3756 : 6944 : + if (thd && !did_enter_cond) 3757 : : + { 3758 : 6864 : + THD_ENTER_COND(thd, &binlog_durable_cond, &binlog_durable_mutex, 3759 : : + &stage_master_has_sent_all_binlog_to_slave, &old_stage); 3760 : 6864 : + did_enter_cond= true; 3761 : : + } 3762 : 6944 : + if (abstime) 3763 : : + { 3764 : 6930 : + int res= mysql_cond_timedwait(&binlog_durable_cond, 3765 : : + &binlog_durable_mutex, 3766 : : + abstime); 3767 : 6928 : + if (res == ETIMEDOUT) 3768 : : + { 3769 : 63 : + is_timeout= true; 3770 : 63 : + break; 3771 : : + } 3772 : : + } 3773 : : + else 3774 : 14 : + mysql_cond_wait(&binlog_durable_cond, &binlog_durable_mutex); 3775 : 12326 : + } 3776 : : + /* 3777 : : + If there is pending binlog data to durably sync to the redo log, but we 3778 : : + did not do this sync ourselves, then signal another thread (if any) to 3779 : : + wakeup and sync. This is necessary to not lose the sync wakeup signal. 3780 : : + 3781 : : + (We use wake-one rather than wake-all for signalling a pending redo log 3782 : : + sync to avoid wakeup-storm). 3783 : : + */ 3784 : 9426 : + if (pending_sync_lsn != 0) 3785 : 776 : + mysql_cond_signal(&binlog_durable_cond); 3786 : : + 3787 : 9426 : + if (did_enter_cond) 3788 : 6862 : + THD_EXIT_COND(thd, &old_stage); 3789 : : + else 3790 : 2564 : + mysql_mutex_unlock(&binlog_durable_mutex); 3791 : : + 3792 : 9426 : + return is_timeout; 3793 : : +} 3794 : : + 3795 : : + 3796 : : +handler_binlog_reader * 3797 : 1577 : +innodb_get_binlog_reader(bool wait_durable) 3798 : : +{ 3799 : 1577 : + return new ha_innodb_binlog_reader(wait_durable); 3800 : : +} 3801 : : + 3802 : : + 3803 : 1494 : +gtid_search::gtid_search() 3804 : 1494 : + : cur_open_file_no(~(uint64_t)0), cur_open_file_length(0), 3805 : 1494 : + cur_open_file((File)-1) 3806 : : +{ 3807 : : + /* Nothing else. */ 3808 : 1494 : +} 3809 : : + 3810 : : + 3811 : 1494 : +gtid_search::~gtid_search() 3812 : : +{ 3813 : 1494 : + if (cur_open_file >= (File)0) 3814 : 0 : + my_close(cur_open_file, MYF(0)); 3815 : 1494 : +} 3816 : : + 3817 : : + 3818 : : +/** 3819 : : + Search for a GTID position in the binlog. 3820 : : + Find a binlog file_no and an offset into the file that is guaranteed to 3821 : : + be before the target position. It can be a bit earlier, that only means a 3822 : : + bit more of the binlog needs to be scanned to find the real position. 3823 : : + 3824 : : + Returns: 3825 : : + -1 error 3826 : : + 0 Position not found (has been purged) 3827 : : + 1 Position found 3828 : : +*/ 3829 : : +int 3830 : 1494 : +gtid_search::find_gtid_pos(slave_connection_state *pos, 3831 : : + rpl_binlog_state_base *out_state, 3832 : : + uint64_t *out_file_no, uint64_t *out_offset) 3833 : : +{ 3834 : : + uint64_t dummy_xa_ref; 3835 : : + /* 3836 : : + Dirty read, but getting a slightly stale value is no problem, we will just 3837 : : + be starting to scan the binlog file at a slightly earlier position than 3838 : : + necessary. 3839 : : + */ 3840 : 1494 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 3841 : : + 3842 : : + std::unique_ptr 3843 : 2988 : + page_buf(static_cast(ut_malloc(ibb_page_size, mem_key_binlog)), 3844 : 4482 : + [](byte *p) {ut_free(p);}); 3845 : 1494 : + if (page_buf == nullptr) 3846 : : + { 3847 : 0 : + my_error(ER_OUTOFMEMORY, MYF(0), ibb_page_size); 3848 : 0 : + return -1; 3849 : : + } 3850 : 1494 : + binlog_chunk_reader chunk_reader(binlog_cur_durable_offset); 3851 : 1494 : + chunk_reader.set_page_buf(page_buf.get()); 3852 : : + 3853 : : + /* First search backwards for the right file to start from. */ 3854 : 1494 : + uint64_t diff_state_page_interval= 0; 3855 : 1494 : + rpl_binlog_state_base base_state, page0_diff_state, tmp_diff_state; 3856 : 1494 : + base_state.init(); 3857 : : + for (;;) 3858 : : + { 3859 : : + /* Read the header page, needed to get the binlog diff state interval. */ 3860 : : + binlog_header_data header; 3861 : 2682 : + chunk_reader.seek(file_no, 0); 3862 : 2682 : + int res= chunk_reader.get_file_header(&header); 3863 : 2682 : + if (UNIV_UNLIKELY(res < 0)) 3864 : 0 : + return -1; 3865 : 2682 : + if (UNIV_UNLIKELY(res == 0)) 3866 : 0 : + goto not_found_in_file; 3867 : 2682 : + diff_state_page_interval= header.diff_state_interval; 3868 : : + 3869 : 2682 : + chunk_reader.seek(file_no, ibb_page_size); 3870 : 2682 : + res= read_gtid_state(&chunk_reader, &base_state, &dummy_xa_ref); 3871 : 2682 : + if (UNIV_UNLIKELY(res < 0)) 3872 : 0 : + return -1; 3873 : 2682 : + if (res == 0) 3874 : : + { 3875 : 0 : + not_found_in_file: 3876 : 0 : + if (file_no == 0) 3877 : : + { 3878 : : + /* Handle the special case of a completely empty binlog file. */ 3879 : 0 : + out_state->reset_nolock(); 3880 : 0 : + *out_file_no= file_no; 3881 : 0 : + *out_offset= ibb_page_size; 3882 : 0 : + return 1; 3883 : : + } 3884 : : + /* If GTID state is not (durably) available, try the previous file. */ 3885 : : + } 3886 : 2682 : + else if (base_state.is_before_pos(pos)) 3887 : 1494 : + break; 3888 : 1188 : + base_state.reset_nolock(); 3889 : 1188 : + if (file_no <= earliest_binlog_file_no) 3890 : 0 : + return 0; 3891 : 1188 : + --file_no; 3892 : 1188 : + } 3893 : : + 3894 : : + /* 3895 : : + Then binary search for the last differential state record that is still 3896 : : + before the searched position. 3897 : : + 3898 : : + The invariant is that page2 is known to be after the target page, and page0 3899 : : + is known to be a valid position to start (but possibly earlier than needed). 3900 : : + */ 3901 : 1494 : + uint32_t page0= 0; 3902 : 1494 : + uint32_t page2= (uint32_t) (diff_state_page_interval + 3903 : 1494 : + ((chunk_reader.cur_end_offset - 1) >> ibb_page_size_shift)); 3904 : : + /* Round to the next diff_state_page_interval after file end. */ 3905 : 1494 : + page2-= page2 % (uint32_t)diff_state_page_interval; 3906 : 1494 : + uint32_t page1= page0 + 3907 : 1494 : + ((page2 - page0) / 3908 : 1494 : + (2*(uint32_t)diff_state_page_interval) * 3909 : : + (uint32_t)diff_state_page_interval); 3910 : 1494 : + page0_diff_state.init(); 3911 : 1494 : + page0_diff_state.load_nolock(&base_state); 3912 : 1494 : + tmp_diff_state.init(); 3913 : 2567 : + while (page1 >= page0 + diff_state_page_interval && page1 > 1) 3914 : : + { 3915 : 1073 : + ut_ad((page1 - page0) % diff_state_page_interval == 0); 3916 : 1073 : + tmp_diff_state.reset_nolock(); 3917 : 1073 : + tmp_diff_state.load_nolock(&base_state); 3918 : 1073 : + chunk_reader.seek(file_no, page1 << ibb_page_size_shift); 3919 : 1073 : + int res= read_gtid_state(&chunk_reader, &tmp_diff_state, &dummy_xa_ref); 3920 : 1073 : + if (UNIV_UNLIKELY(res < 0)) 3921 : 0 : + return -1; 3922 : 1073 : + if (res == 0) 3923 : : + { 3924 : : + /* 3925 : : + If the diff state record was not written here for some reason, just 3926 : : + try the one just before. It will be safe, even if not always optimal, 3927 : : + and this is an abnormal situation anyway. 3928 : : + */ 3929 : 0 : + page1= page1 - (uint32_t)diff_state_page_interval; 3930 : 0 : + continue; 3931 : : + } 3932 : 1073 : + if (tmp_diff_state.is_before_pos(pos)) 3933 : : + { 3934 : 535 : + page0= page1; 3935 : 535 : + page0_diff_state.reset_nolock(); 3936 : 535 : + page0_diff_state.load_nolock(&tmp_diff_state); 3937 : : + } 3938 : : + else 3939 : 538 : + page2= page1; 3940 : 1073 : + page1= page0 + 3941 : 1073 : + ((page2 - page0) / 3942 : 1073 : + (2*(uint32_t)diff_state_page_interval) * 3943 : : + (uint32_t)diff_state_page_interval); 3944 : : + } 3945 : 1494 : + ut_ad(page1 >= page0); 3946 : 1494 : + out_state->load_nolock(&page0_diff_state); 3947 : 1494 : + *out_file_no= file_no; 3948 : 1494 : + if (page0 == 0) 3949 : 1161 : + page0= 1; /* Skip the initial file header page. */ 3950 : 1494 : + *out_offset= (uint64_t)page0 << ibb_page_size_shift; 3951 : 1494 : + return 1; 3952 : 1494 : +} 3953 : : + 3954 : : + 3955 : : +int 3956 : 1494 : +ha_innodb_binlog_reader::init_gtid_pos(THD *thd, slave_connection_state *pos, 3957 : : + rpl_binlog_state_base *state) 3958 : : +{ 3959 : 1494 : + gtid_search search_obj; 3960 : : + uint64_t file_no; 3961 : : + uint64_t offset; 3962 : : + 3963 : : + /* 3964 : : + Wait for at least the initial GTID state record to become durable before 3965 : : + looking for the starting GTID position. 3966 : : + This is unlikely to need to wait, as it would imply that _no_ part of the 3967 : : + binlog is durable at this point. But it might theoretically occur perhaps 3968 : : + after a PURGE of all binlog files but the active; and failing to do the 3969 : : + wait if needed might wrongly return an error that the GTID position is 3970 : : + too old. 3971 : : + */ 3972 : 1494 : + chunk_rd.seek(earliest_binlog_file_no, ibb_page_size); 3973 : 1494 : + if (UNIV_UNLIKELY(wait_available(thd, nullptr))) 3974 : 0 : + return -1; 3975 : : + 3976 : 1494 : + int res= search_obj.find_gtid_pos(pos, state, &file_no, &offset); 3977 : 1494 : + if (res < 0) 3978 : 0 : + return -1; 3979 : 1494 : + if (res > 0) 3980 : : + { 3981 : 1494 : + requested_file_no= file_no; 3982 : 1494 : + chunk_rd.seek(file_no, offset); 3983 : 1494 : + chunk_rd.skip_partial(true); 3984 : 1494 : + cur_file_no= chunk_rd.current_file_no(); 3985 : 1494 : + cur_file_pos= chunk_rd.current_pos(); 3986 : : + } 3987 : 1494 : + return res; 3988 : 1494 : +} 3989 : : + 3990 : : + 3991 : : +int 3992 : 83 : +ha_innodb_binlog_reader::init_legacy_pos(THD *thd, const char *filename, 3993 : : + ulonglong offset) 3994 : : +{ 3995 : : + uint64_t file_no; 3996 : 83 : + if (!filename) 3997 : : + { 3998 : 9 : + mysql_mutex_lock(&purge_binlog_mutex); 3999 : 9 : + file_no= earliest_binlog_file_no; 4000 : 9 : + mysql_mutex_unlock(&purge_binlog_mutex); 4001 : : + } 4002 : 74 : + else if (!is_binlog_name(filename, &file_no)) 4003 : : + { 4004 : 4 : + my_error(ER_UNKNOWN_TARGET_BINLOG, MYF(0)); 4005 : 4 : + return -1; 4006 : : + } 4007 : 79 : + if (file_no > active_binlog_file_no.load(std::memory_order_acquire)) 4008 : : + { 4009 : 4 : + my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), "SHOW BINLOG EVENTS", 4010 : : + "Could not find target log"); 4011 : 4 : + return -1; 4012 : : + } 4013 : 75 : + requested_file_no= file_no; 4014 : 75 : + if ((uint64_t)offset >= (uint64_t)(UINT32_MAX) << ibb_page_size_shift) 4015 : : + { 4016 : 0 : + my_error(ER_BINLOG_POS_INVALID, MYF(0), offset); 4017 : 0 : + return -1; 4018 : : + } 4019 : : + 4020 : 75 : + if (offset < ibb_page_size) 4021 : 41 : + offset= ibb_page_size; 4022 : : + 4023 : : + /* 4024 : : + Start at the beginning of the page containing the requested position. Then 4025 : : + read forwards until the requested position is reached. This way we avoid 4026 : : + reading garbaga data for invalid request offset. 4027 : : + */ 4028 : : + 4029 : 75 : + chunk_rd.seek(file_no, 4030 : 75 : + (uint64_t)offset & ((uint64_t)~0 << ibb_page_size_shift)); 4031 : : + int err= 4032 : 75 : + chunk_rd.find_offset_in_page((uint32_t)(offset & (ibb_page_size - 1))); 4033 : 75 : + chunk_rd.release(true); 4034 : 75 : + chunk_rd.skip_partial(true); 4035 : : + 4036 : 75 : + cur_file_no= chunk_rd.current_file_no(); 4037 : 75 : + cur_file_pos= chunk_rd.current_pos(); 4038 : 75 : + return err; 4039 : : +} 4040 : : + 4041 : : + 4042 : : +void 4043 : 71 : +ha_innodb_binlog_reader::enable_single_file() 4044 : : +{ 4045 : 71 : + chunk_rd.stop_file_no= requested_file_no != ~(uint64_t)0 ? 4046 : : + requested_file_no : chunk_rd.s.file_no; 4047 : 71 : +} 4048 : : + 4049 : : + 4050 : : +void 4051 : 2 : +ha_innodb_binlog_reader::seek_internal(uint64_t file_no, uint64_t offset) 4052 : : +{ 4053 : 2 : + chunk_rd.seek(file_no, offset); 4054 : 2 : + chunk_rd.skip_partial(true); 4055 : 2 : + cur_file_no= chunk_rd.current_file_no(); 4056 : 2 : + cur_file_pos= chunk_rd.current_pos(); 4057 : 2 : +} 4058 : : + 4059 : : + 4060 : : +void 4061 : 590 : +ibb_wait_durable_offset(uint64_t file_no, uint64_t wait_offset) 4062 : : +{ 4063 : : + uint64_t dur_offset= 4064 : 590 : + binlog_cur_durable_offset[file_no & 3].load(std:: memory_order_relaxed); 4065 : 590 : + if (dur_offset >= wait_offset) 4066 : 10 : + return; 4067 : : + 4068 : 580 : + ha_innodb_binlog_reader reader(true, file_no, dur_offset); 4069 : : + for (;;) 4070 : : + { 4071 : 582 : + reader.wait_available(nullptr, nullptr); 4072 : : + dur_offset= 4073 : 582 : + binlog_cur_durable_offset[file_no & 3].load(std:: memory_order_relaxed); 4074 : 582 : + if (dur_offset >= wait_offset) 4075 : 580 : + break; 4076 : 2 : + reader.seek_internal(file_no, dur_offset); 4077 : : + } 4078 : 580 : +} 4079 : : + 4080 : : + 4081 : 16671 : +pending_lsn_fifo::pending_lsn_fifo() 4082 : 16671 : + : flushing_lsn(0), last_lsn_added(0), cur_file_no(~(uint64_t)0), 4083 : 16671 : + head(0), tail(0) 4084 : : +{ 4085 : 16671 : +} 4086 : : + 4087 : : + 4088 : : +void 4089 : 1883 : +pending_lsn_fifo::init(uint64_t start_file_no) 4090 : : +{ 4091 : 1883 : + mysql_mutex_lock(&binlog_durable_mutex); 4092 : 1883 : + ut_ad(cur_file_no == ~(uint64_t)0); 4093 : 1883 : + cur_file_no= start_file_no; 4094 : 1883 : + mysql_mutex_unlock(&binlog_durable_mutex); 4095 : 1883 : +} 4096 : : + 4097 : : + 4098 : : +void 4099 : 1714 : +pending_lsn_fifo::reset() 4100 : : +{ 4101 : 1714 : + mysql_mutex_lock(&binlog_durable_mutex); 4102 : 1714 : + cur_file_no= ~(uint64_t)0; 4103 : 1714 : + mysql_mutex_unlock(&binlog_durable_mutex); 4104 : 1714 : +} 4105 : : + 4106 : : + 4107 : : +bool 4108 : 176722 : +pending_lsn_fifo::process_durable_lsn(lsn_t lsn) 4109 : : +{ 4110 : 176722 : + mysql_mutex_assert_owner(&binlog_durable_mutex); 4111 : 176722 : + ut_ad(cur_file_no != ~(uint64_t)0); 4112 : : + 4113 : 176722 : + entry *got= nullptr; 4114 : : + for (;;) 4115 : : + { 4116 : 331690 : + if (is_empty()) 4117 : 153125 : + break; 4118 : 178565 : + entry &e= cur_tail(); 4119 : 178565 : + if (lsn < e.lsn) 4120 : 23597 : + break; 4121 : 154968 : + got= &e; 4122 : 154968 : + drop_tail(); 4123 : 154968 : + } 4124 : 176722 : + if (got) 4125 : : + { 4126 : 143509 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 4127 : 143509 : + DBUG_EXECUTE_IF("block_binlog_durable", active= got->file_no + 2;); 4128 : 143509 : + if (got->file_no + 1 >= active) 4129 : : + { 4130 : : + /* 4131 : : + We must never set the durable offset back to a prior value. 4132 : : + This should be assured by never adding a smaller lsn into the fifo than 4133 : : + any prior lsn added, and checked by this assertion. 4134 : : + */ 4135 : 286886 : + ut_ad(binlog_cur_durable_offset[got->file_no & 3]. 4136 : : + load(std::memory_order_relaxed) <= got->offset); 4137 : 143443 : + binlog_cur_durable_offset[got->file_no & 3].store 4138 : 143443 : + (got->offset, std::memory_order_relaxed); 4139 : : + } 4140 : : + /* 4141 : : + If we moved the durable point to the next file_no, mark the prior 4142 : : + file_no as now fully durable. 4143 : : + Since we only ever have at most two binlog tablespaces open, and since 4144 : : + we make file_no=N fully durable (by calling into this function) before 4145 : : + pre-allocating N+2, we can only ever move ahead one file_no at a time 4146 : : + here. 4147 : : + */ 4148 : 143509 : + if (cur_file_no != got->file_no) 4149 : : + { 4150 : 1395 : + ut_ad(got->file_no == cur_file_no + 1); 4151 : 1395 : + binlog_cur_durable_offset[cur_file_no & 3].store( 4152 : 1395 : + binlog_cur_end_offset[cur_file_no & 3].load(std::memory_order_relaxed), 4153 : : + std::memory_order_relaxed); 4154 : 1395 : + cur_file_no= got->file_no; 4155 : : + } 4156 : 143509 : + mysql_cond_broadcast(&binlog_durable_cond); 4157 : 143509 : + return true; 4158 : : + } 4159 : 33213 : + return false; 4160 : : +} 4161 : : + 4162 : : + 4163 : : +/** 4164 : : + After a binlog commit, put the LSN and the corresponding binlog position 4165 : : + into the ibb_pending_lsn_fifo. We do this here (rather than immediately in 4166 : : + innodb_binlog_post_commit()), so that we can delay it until we are no longer 4167 : : + holding more critical locks that could block other writers. As we will be 4168 : : + contending with readers here on binlog_durable_mutex. 4169 : : +*/ 4170 : : +void 4171 : 153409 : +pending_lsn_fifo::record_commit(binlog_oob_context *c) 4172 : : +{ 4173 : 153409 : + uint64_t pending_file_no= c->pending_file_no; 4174 : 153409 : + if (pending_file_no == ~(uint64_t)0) 4175 : 587 : + return; 4176 : 152822 : + c->pending_file_no= ~(uint64_t)0; 4177 : 152822 : + lsn_t pending_lsn= c->pending_lsn; 4178 : 152822 : + uint64_t pending_offset= c->pending_offset; 4179 : 152822 : + add_to_fifo(pending_lsn, pending_file_no, pending_offset); 4180 : : +} 4181 : : + 4182 : : + 4183 : : +void 4184 : 155089 : +pending_lsn_fifo::add_to_fifo(uint64_t lsn, uint64_t file_no, uint64_t offset) 4185 : : +{ 4186 : 155089 : + mysql_mutex_lock(&binlog_durable_mutex); 4187 : : + /* 4188 : : + The record_commit() operation is done outside of critical locks for 4189 : : + scalabitily, so can occur out-of-order. So only insert the new entry if 4190 : : + it is newer than any previously inserted. 4191 : : + */ 4192 : 155102 : + ut_ad(is_empty() || cur_head().lsn == last_lsn_added); 4193 : 155102 : + if (lsn > last_lsn_added) 4194 : : + { 4195 : 154968 : + if (is_full()) 4196 : : + { 4197 : : + /* 4198 : : + When the fifo is full, we just overwrite the head with a newer LSN. 4199 : : + This way, whenever _some_ LSN gets synced durably to disk, we will 4200 : : + always be able to make some progress and clear some fifo entries. And 4201 : : + when this latest LSN gets eventually synced, any overwritten entry 4202 : : + will progress as well. 4203 : : + */ 4204 : : + } 4205 : : + else 4206 : : + { 4207 : : + /* 4208 : : + Insert a new head. 4209 : : + Note that we make the fifo size a power-of-two (1 <(p)->xid); 4241 : 517 : + *out_len= xid->key_length(); 4242 : 517 : + return xid->key(); 4243 : : +} 4244 : : + 4245 : : + 4246 : 11023 : +ibb_xid_hash::ibb_xid_hash() 4247 : : +{ 4248 : 11023 : + mysql_mutex_init(ibb_xid_hash_mutex_key, &xid_mutex, nullptr); 4249 : 11023 : + my_hash_init(mem_key_binlog, &xid_hash, &my_charset_bin, 32, 0, 4250 : : + sizeof(XID), get_xid_hash_key, nullptr, MYF(HASH_UNIQUE)); 4251 : 11023 : +} 4252 : : + 4253 : : + 4254 : 9425 : +ibb_xid_hash::~ibb_xid_hash() 4255 : : +{ 4256 : 9449 : + for (uint32 i= 0; i < xid_hash.records; ++i) 4257 : 24 : + my_free(my_hash_element(&xid_hash, i)); 4258 : 9425 : + my_hash_free(&xid_hash); 4259 : 9425 : + mysql_mutex_destroy(&xid_mutex); 4260 : 9425 : +} 4261 : : + 4262 : : + 4263 : : +bool 4264 : 92 : +ibb_xid_hash::add_xid(const XID *xid, const binlog_oob_context *c) 4265 : : +{ 4266 : 92 : + if (UNIV_LIKELY(c->node_list_len > 0)) 4267 : : + { 4268 : 72 : + uint32_t last= c->node_list_len-1; 4269 : 144 : + return add_xid(xid, c->first_node_file_no, c->lf_pins, 4270 : 72 : + c->node_list[last].node_index + 1, 4271 : 72 : + c->first_node_file_no, c->first_node_offset, 4272 : 72 : + c->node_list[last].file_no, c->node_list[last].offset); 4273 : : + } 4274 : : + else 4275 : : + { 4276 : : + /* 4277 : : + Empty XA transaction, but we still need to ensure the prepare record 4278 : : + is kept until the (empty) transactions gets XA COMMMIT'ted. 4279 : : + */ 4280 : : + uint64_t refcnt_file_no= 4281 : 20 : + active_binlog_file_no.load(std::memory_order_acquire); 4282 : 20 : + return add_xid(xid, refcnt_file_no, c->lf_pins, 0, 0, 0, 0, 0); 4283 : : + } 4284 : : +} 4285 : : + 4286 : : + 4287 : : +bool 4288 : 120 : +ibb_xid_hash::add_xid(const XID *xid, uint64_t refcnt_file_no, LF_PINS *pins, 4289 : : + uint64_t num_nodes, 4290 : : + uint64_t first_file_no, uint64_t first_offset, 4291 : : + uint64_t last_file_no, uint64_t last_offset) 4292 : : +{ 4293 : : + xid_elem *e= 4294 : 120 : + (xid_elem *)my_malloc(mem_key_binlog, sizeof(xid_elem), MYF(MY_WME)); 4295 : 120 : + if (UNIV_UNLIKELY(!e)) 4296 : : + { 4297 : 0 : + my_error(ER_OUTOFMEMORY, MYF(0), (int)sizeof(xid_elem)); 4298 : 0 : + return true; 4299 : : + } 4300 : 120 : + e->xid.set(xid); 4301 : 120 : + e->oob_num_nodes= num_nodes; 4302 : 120 : + e->oob_first_file_no= first_file_no; 4303 : 120 : + e->oob_first_offset= first_offset; 4304 : 120 : + e->oob_last_file_no= last_file_no; 4305 : 120 : + e->oob_last_offset= last_offset; 4306 : 120 : + e->refcnt_file_no= refcnt_file_no; 4307 : 120 : + mysql_mutex_lock(&xid_mutex); 4308 : 120 : + if (my_hash_insert(&xid_hash, (uchar *)e)) 4309 : : + { 4310 : 0 : + mysql_mutex_unlock(&xid_mutex); 4311 : 0 : + my_free(e); 4312 : 0 : + return true; 4313 : : + } 4314 : : + uint64_t refcnt= 4315 : 120 : + ibb_file_hash.oob_ref_inc(refcnt_file_no, pins, true); 4316 : 120 : + if (refcnt == 1) 4317 : 110 : + ibb_file_hash.update_earliest_xa_ref(refcnt_file_no, pins); 4318 : 120 : + mysql_mutex_unlock(&xid_mutex); 4319 : 120 : + return false; 4320 : : +} 4321 : : + 4322 : : + 4323 : : +template bool 4324 : 28 : +ibb_xid_hash::run_on_xid(const XID *xid, F callback) 4325 : : +{ 4326 : 28 : + size_t key_len= 0; 4327 : 28 : + const uchar *key_ptr= get_xid_hash_key(xid, &key_len, 1); 4328 : : + bool err; 4329 : : + 4330 : 28 : + mysql_mutex_lock(&xid_mutex); 4331 : 28 : + uchar *rec= my_hash_search(&xid_hash, key_ptr, key_len); 4332 : 28 : + if (UNIV_LIKELY(rec != nullptr)) 4333 : : + { 4334 : 28 : + err= callback(reinterpret_cast(rec)); 4335 : : + } 4336 : : + else 4337 : 0 : + err= true; 4338 : 28 : + mysql_mutex_unlock(&xid_mutex); 4339 : 28 : + return err; 4340 : : +} 4341 : : + 4342 : : + 4343 : : +/* 4344 : : + Look up an XID in the internal XID hash. 4345 : : + Remove the entry found (if any) and return it. 4346 : : +*/ 4347 : : +ibb_xid_hash::xid_elem * 4348 : 96 : +ibb_xid_hash::grab_xid(const XID *xid) 4349 : : +{ 4350 : 96 : + xid_elem *e= nullptr; 4351 : 96 : + size_t key_len= 0; 4352 : 96 : + const uchar *key_ptr= get_xid_hash_key(xid, &key_len, 1); 4353 : 96 : + mysql_mutex_lock(&xid_mutex); 4354 : 96 : + uchar *rec= my_hash_search(&xid_hash, key_ptr, key_len); 4355 : 96 : + if (UNIV_LIKELY(rec != nullptr)) 4356 : : + { 4357 : 96 : + e= reinterpret_cast(rec); 4358 : 96 : + my_hash_delete(&xid_hash, rec); 4359 : : + } 4360 : 96 : + mysql_mutex_unlock(&xid_mutex); 4361 : 96 : + return e; 4362 : : +} 4363 : : + 4364 : : + 4365 : : +void 4366 : 22864 : +ibb_get_filename(char name[FN_REFLEN], uint64_t file_no) 4367 : : +{ 4368 : : + static_assert(BINLOG_NAME_MAX_LEN <= FN_REFLEN, 4369 : : + "FN_REFLEN too shot to hold InnoDB binlog name"); 4370 : 22864 : + binlog_name_make_short(name, file_no); 4371 : 22864 : +} 4372 : : + 4373 : : + 4374 : : +extern "C" void binlog_get_cache(THD *, uint64_t, uint64_t, IO_CACHE **, 4375 : : + handler_binlog_event_group_info **, 4376 : : + const rpl_gtid **); 4377 : : + 4378 : : +binlog_oob_context * 4379 : 390981 : +innodb_binlog_trx(trx_t *trx, mtr_t *mtr) 4380 : : +{ 4381 : : + IO_CACHE *cache; 4382 : : + handler_binlog_event_group_info *binlog_info; 4383 : : + const rpl_gtid *gtid; 4384 : : + uint64_t file_no, pos; 4385 : : + 4386 : 390981 : + if (!trx->mysql_thd) 4387 : 0 : + return nullptr; 4388 : 390981 : + innodb_binlog_status(&file_no, &pos); 4389 : 390981 : + binlog_get_cache(trx->mysql_thd, file_no, pos, &cache, &binlog_info, >id); 4390 : 390981 : + if (UNIV_LIKELY(binlog_info != nullptr) && 4391 : 133993 : + UNIV_LIKELY(binlog_info->gtid_offset > 0)) { 4392 : 133993 : + binlog_full_state.update_nolock(gtid); 4393 : 133993 : + binlog_diff_state.update_nolock(gtid); 4394 : 133993 : + innodb_binlog_write_cache(cache, binlog_info, mtr); 4395 : 133993 : + return static_cast(binlog_info->engine_ptr); 4396 : : + } 4397 : 256988 : + return nullptr; 4398 : : +} 4399 : : + 4400 : : + 4401 : : +void 4402 : 1384064 : +innodb_binlog_post_commit(mtr_t *mtr, binlog_oob_context *c) 4403 : : +{ 4404 : 1384064 : + if (c) 4405 : : + { 4406 : 156353 : + c->pending_lsn= mtr->commit_lsn(); 4407 : 156353 : + ut_ad(c->pending_lsn != 0); 4408 : : + } 4409 : 1384064 : +} 4410 : : + 4411 : : + 4412 : : +/* 4413 : : + Function to record the write of a record to the binlog, when done outside 4414 : : + of a normal binlog commit, eg. XA PREPARE or XA ROLLBACK. 4415 : : +*/ 4416 : : +static void 4417 : 80 : +innodb_binlog_post_write_rec(mtr_t *mtr, binlog_oob_context *c) 4418 : : +{ 4419 : 80 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 4420 : 80 : + c->pending_file_no= file_no; 4421 : 80 : + c->pending_offset= 4422 : 80 : + binlog_cur_end_offset[file_no & 3].load(std::memory_order_relaxed); 4423 : 80 : + innodb_binlog_post_commit(mtr, c); 4424 : 80 : +} 4425 : : + 4426 : : + 4427 : : +bool 4428 : 4985 : +innobase_binlog_write_direct_ordered(IO_CACHE *cache, 4429 : : + handler_binlog_event_group_info *binlog_info, 4430 : : + const rpl_gtid *gtid) 4431 : : +{ 4432 : 4985 : + mtr_t mtr{nullptr}; 4433 : 4985 : + ut_ad(binlog_info->engine_ptr2 == nullptr); 4434 : 4985 : + if (gtid) 4435 : : + { 4436 : 4816 : + binlog_full_state.update_nolock(gtid); 4437 : 4816 : + binlog_diff_state.update_nolock(gtid); 4438 : : + } 4439 : 4985 : + innodb_binlog_status(&binlog_info->out_file_no, &binlog_info->out_offset); 4440 : 4985 : + mtr.start(); 4441 : 4985 : + innodb_binlog_write_cache(cache, binlog_info, &mtr); 4442 : 4985 : + mtr.commit(); 4443 : 4985 : + innodb_binlog_post_commit(&mtr, static_cast 4444 : 4985 : + (binlog_info->engine_ptr)); 4445 : 4985 : + return false; 4446 : 4985 : +} 4447 : : + 4448 : : + 4449 : : +bool 4450 : 4985 : +innobase_binlog_write_direct(IO_CACHE *cache, 4451 : : + handler_binlog_event_group_info *binlog_info, 4452 : : + const rpl_gtid *gtid) 4453 : : +{ 4454 : 4985 : + ut_ad(binlog_info->engine_ptr2 == nullptr); 4455 : 4985 : + binlog_oob_context *c= 4456 : : + static_cast(binlog_info->engine_ptr); 4457 : 4985 : + if (UNIV_LIKELY(c != nullptr)) 4458 : : + { 4459 : 4985 : + if (srv_flush_log_at_trx_commit & 1) 4460 : 4983 : + log_write_up_to(c->pending_lsn, true); 4461 : 4985 : + DEBUG_SYNC(current_thd, "ibb_after_commit_redo_log"); 4462 : 4985 : + ibb_pending_lsn_fifo.record_commit(c); 4463 : : + } 4464 : 4985 : + return false; 4465 : : +} 4466 : : + 4467 : : + 4468 : : +void 4469 : 131063 : +ibb_group_commit(THD *thd, handler_binlog_event_group_info *binlog_info) 4470 : : +{ 4471 : 131063 : + binlog_oob_context *c= 4472 : : + static_cast(binlog_info->engine_ptr); 4473 : 131063 : + if (UNIV_LIKELY(c != nullptr)) 4474 : : + { 4475 : 131063 : + if (srv_flush_log_at_trx_commit & 1 && c->pending_lsn) 4476 : : + { 4477 : : + /* 4478 : : + Sync the InnoDB redo log durably to disk here for the entire group 4479 : : + commit, so that it will be available for all binlog readers. 4480 : : + */ 4481 : 130983 : + log_write_up_to(c->pending_lsn, true); 4482 : : + } 4483 : 131060 : + DEBUG_SYNC(current_thd, "ibb_after_group_commit_redo_log"); 4484 : 131050 : + ibb_pending_lsn_fifo.record_commit(c); 4485 : : + } 4486 : 131063 : +} 4487 : : + 4488 : : + 4489 : : +bool 4490 : 52 : +ibb_write_xa_prepare_ordered(THD *thd, 4491 : : + handler_binlog_event_group_info *binlog_info, 4492 : : + uchar engine_count) 4493 : : +{ 4494 : 52 : + mtr_t mtr{nullptr}; 4495 : 52 : + binlog_oob_context *c= 4496 : : + static_cast(binlog_info->engine_ptr); 4497 : 52 : + chunk_data_xa_prepare chunk_data(binlog_info->xa_xid, engine_count, c); 4498 : 52 : + mtr.start(); 4499 : 52 : + fsp_binlog_write_rec(&chunk_data, &mtr, FSP_BINLOG_TYPE_XA_PREPARE, 4500 : : + c->lf_pins); 4501 : 52 : + mtr.commit(); 4502 : 52 : + innodb_binlog_post_write_rec(&mtr, c); 4503 : : + 4504 : 52 : + return false; 4505 : 52 : +} 4506 : : + 4507 : : + 4508 : : +bool 4509 : 52 : +ibb_write_xa_prepare(THD *thd, 4510 : : + handler_binlog_event_group_info *binlog_info, 4511 : : + uchar engine_count) 4512 : : +{ 4513 : 52 : + bool err= false; 4514 : : + 4515 : 52 : + binlog_oob_context *c= 4516 : : + static_cast(binlog_info->engine_ptr); 4517 : 52 : + ut_ad(binlog_info->xa_xid != nullptr); 4518 : 52 : + if (ibb_xa_xid_hash->add_xid(binlog_info->xa_xid, c)) 4519 : 0 : + err= true; 4520 : : + 4521 : : + /* 4522 : : + Sync the redo log to ensure that the prepare record is durably written to 4523 : : + disk. This is necessary before returning OK to the client, to be sure we 4524 : : + can recover the binlog part of the XA transaction in case of crash. 4525 : : + */ 4526 : 52 : + if (srv_flush_log_at_trx_commit > 0) 4527 : 52 : + log_write_up_to(c->pending_lsn, (srv_flush_log_at_trx_commit & 1)); 4528 : 52 : + DEBUG_SYNC(thd, "ibb_after_prepare_redo_log"); 4529 : 52 : + ibb_pending_lsn_fifo.record_commit(c); 4530 : : + 4531 : 52 : + return err; 4532 : : +} 4533 : : + 4534 : : + 4535 : : +bool 4536 : 28 : +ibb_xa_rollback_ordered(THD *thd, const XID *xid, void **engine_data) 4537 : : +{ 4538 : 28 : + binlog_oob_context *c= 4539 : : + static_cast(*engine_data); 4540 : 28 : + if (UNIV_UNLIKELY(c == nullptr)) 4541 : 8 : + *engine_data= c= alloc_oob_context(); 4542 : : + 4543 : : + /* 4544 : : + Write ROLLBACK record to the binlog. 4545 : : + This will be used during recovery to know that the XID is no longer active, 4546 : : + allowing purge of the associated binlogs. 4547 : : + */ 4548 : 28 : + chunk_data_xa_complete chunk_data(xid, false); 4549 : 28 : + mtr_t mtr{nullptr}; 4550 : 28 : + mtr.start(); 4551 : 28 : + fsp_binlog_write_rec(&chunk_data, &mtr, FSP_BINLOG_TYPE_XA_COMPLETE, 4552 : : + c->lf_pins); 4553 : 28 : + mtr.commit(); 4554 : 28 : + innodb_binlog_post_write_rec(&mtr, c); 4555 : : + 4556 : 28 : + return false; 4557 : 28 : +} 4558 : : + 4559 : : + 4560 : : +bool 4561 : 28 : +ibb_xa_rollback(THD *thd, const XID *xid, void **engine_data) 4562 : : +{ 4563 : 28 : + binlog_oob_context *c= 4564 : : + static_cast(*engine_data); 4565 : : + 4566 : : + /* 4567 : : + Keep the reference count here, as we need the rollback record to be 4568 : : + available for recovery until all engines have durably rolled back. 4569 : : + Decrement will happen after that, in ibb_binlog_unlog(). 4570 : : + */ 4571 : : + 4572 : : + /* 4573 : : + Durably write the rollback record to disk. This way, when we return the 4574 : : + "ok" packet to the client, we are sure that crash recovery will make the 4575 : : + XID rollback in engines if needed. 4576 : : + */ 4577 : 28 : + ut_ad(c->pending_lsn > 0); 4578 : 28 : + if (srv_flush_log_at_trx_commit > 0) 4579 : 28 : + log_write_up_to(c->pending_lsn, (srv_flush_log_at_trx_commit & 1)); 4580 : 28 : + DEBUG_SYNC(thd, "ibb_after_rollback_redo_log"); 4581 : : + 4582 : 28 : + ibb_pending_lsn_fifo.record_commit(c); 4583 : 28 : + c->pending_lsn= 0; 4584 : 28 : + return false; 4585 : : +} 4586 : : + 4587 : : + 4588 : : +void 4589 : 96 : +ibb_binlog_unlog(const XID *xid, void **engine_data) 4590 : : +{ 4591 : 96 : + binlog_oob_context *c= 4592 : : + static_cast(*engine_data); 4593 : 96 : + if (UNIV_UNLIKELY(c == nullptr)) 4594 : 0 : + *engine_data= c= alloc_oob_context(); 4595 : 96 : + ibb_xid_hash::xid_elem *elem= ibb_xa_xid_hash->grab_xid(xid); 4596 : 96 : + if (elem) 4597 : : + { 4598 : 96 : + mysql_mutex_lock(&ibb_xa_xid_hash->xid_mutex); 4599 : : + uint64_t new_refcnt= 4600 : 96 : + ibb_file_hash.oob_ref_dec(elem->refcnt_file_no, c->lf_pins, true); 4601 : 96 : + if (new_refcnt == 0) 4602 : 88 : + ibb_file_hash.update_earliest_xa_ref(elem->refcnt_file_no, c->lf_pins); 4603 : 96 : + mysql_mutex_unlock(&ibb_xa_xid_hash->xid_mutex); 4604 : 96 : + my_free(elem); 4605 : : + } 4606 : 96 : +} 4607 : : + 4608 : : + 4609 : : +bool 4610 : 556 : +innodb_find_binlogs(uint64_t *out_first, uint64_t *out_last) 4611 : : +{ 4612 : 556 : + mysql_mutex_lock(&active_binlog_mutex); 4613 : 556 : + *out_last= last_created_binlog_file_no; 4614 : 556 : + mysql_mutex_unlock(&active_binlog_mutex); 4615 : 556 : + mysql_mutex_lock(&purge_binlog_mutex); 4616 : 556 : + *out_first= earliest_binlog_file_no; 4617 : 556 : + mysql_mutex_unlock(&purge_binlog_mutex); 4618 : 556 : + if (*out_first == ~(uint64_t)0 || *out_last == ~(uint64_t)0) 4619 : : + { 4620 : 0 : + ut_ad(0 /* Impossible, we wait at startup for binlog to be created. */); 4621 : 0 : + return true; 4622 : : + } 4623 : 556 : + return false; 4624 : : +} 4625 : : + 4626 : : + 4627 : : +void 4628 : 416616 : +innodb_binlog_status(uint64_t *out_file_no, uint64_t *out_pos) 4629 : : +{ 4630 : : + static_assert(BINLOG_NAME_MAX_LEN <= FN_REFLEN, 4631 : : + "FN_REFLEN too shot to hold InnoDB binlog name"); 4632 : 416616 : + uint64_t file_no= active_binlog_file_no.load(std::memory_order_relaxed); 4633 : 416616 : + uint32_t page_no= binlog_cur_page_no; 4634 : 416616 : + uint32_t in_page_offset= binlog_cur_page_offset; 4635 : 416616 : + *out_file_no= file_no; 4636 : 416616 : + *out_pos= ((uint64_t)page_no << ibb_page_size_shift) | in_page_offset; 4637 : 416616 : +} 4638 : : + 4639 : : + 4640 : : +bool 4641 : 6 : +innodb_binlog_get_init_state(rpl_binlog_state_base *out_state) 4642 : : +{ 4643 : 6 : + binlog_chunk_reader chunk_reader(binlog_cur_end_offset); 4644 : 6 : + bool err= false; 4645 : : + uint64_t dummy_xa_ref; 4646 : : + 4647 : 12 : + byte *page_buf= static_cast(ut_malloc(ibb_page_size, mem_key_binlog)); 4648 : 6 : + if (!page_buf) 4649 : : + { 4650 : 0 : + my_error(ER_OUTOFMEMORY, MYF(0), ibb_page_size); 4651 : 0 : + return true; 4652 : : + } 4653 : 6 : + chunk_reader.set_page_buf(page_buf); 4654 : : + 4655 : 6 : + mysql_mutex_lock(&purge_binlog_mutex); 4656 : 6 : + chunk_reader.seek(earliest_binlog_file_no, ibb_page_size); 4657 : 6 : + int res= read_gtid_state(&chunk_reader, out_state, &dummy_xa_ref); 4658 : 6 : + mysql_mutex_unlock(&purge_binlog_mutex); 4659 : 6 : + if (res != 1) 4660 : 0 : + err= true; 4661 : 6 : + ut_free(page_buf); 4662 : 6 : + return err; 4663 : : + 4664 : 6 : +} 4665 : : + 4666 : : + 4667 : : +bool 4668 : 1718 : +innodb_reset_binlogs() 4669 : : +{ 4670 : 1718 : + bool err= false; 4671 : 1718 : + LF_PINS *lf_pins= lf_hash_get_pins(&ibb_file_hash.hash); 4672 : 1718 : + ut_a(lf_pins); 4673 : 1718 : + ut_a(innodb_binlog_inited >= 2); 4674 : : + 4675 : 1718 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 4676 : 1718 : + if (ibb_file_hash.check_any_oob_ref_in_use(earliest_binlog_file_no, 4677 : : + active, lf_pins)) 4678 : : + { 4679 : 4 : + my_error(ER_BINLOG_IN_USE_TRX, MYF(0)); 4680 : 4 : + return true; 4681 : : + } 4682 : : + 4683 : : + /* Close existing binlog tablespaces and stop the pre-alloc thread. */ 4684 : 1714 : + innodb_binlog_close(false); 4685 : : + 4686 : : + /* 4687 : : + Durably flush the redo log to disk. This is mostly to simplify 4688 : : + conceptually (RESET MASTER is not performance critical). This way, we will 4689 : : + never see a state where recovery stops at an LSN prior to the RESET 4690 : : + MASTER, so we do not have any question around truncating the binlog to a 4691 : : + point before the RESET MASTER. 4692 : : + */ 4693 : 1714 : + log_buffer_flush_to_disk(true); 4694 : : + 4695 : : + /* Prevent any flushing activity while resetting. */ 4696 : 1714 : + binlog_page_fifo->lock_wait_for_idle(); 4697 : 1714 : + binlog_page_fifo->reset(); 4698 : 1714 : + ibb_pending_lsn_fifo.reset(); 4699 : : + 4700 : 1714 : + ibb_file_hash.remove_up_to(last_created_binlog_file_no, lf_pins); 4701 : : + 4702 : : + /* Delete all binlog files in the directory. */ 4703 : 1714 : + MY_DIR *dir= my_dir(innodb_binlog_directory, MYF(MY_WME)); 4704 : 1714 : + if (!dir) 4705 : : + { 4706 : 0 : + sql_print_error("Could not read the binlog directory '%s', error code %d", 4707 : 0 : + innodb_binlog_directory, my_errno); 4708 : 0 : + err= true; 4709 : : + } 4710 : : + else 4711 : : + { 4712 : 1714 : + size_t num_entries= dir->number_of_files; 4713 : 1714 : + fileinfo *entries= dir->dir_entry; 4714 : 34746 : + for (size_t i= 0; i < num_entries; ++i) { 4715 : 33032 : + const char *name= entries[i].name; 4716 : : + uint64_t file_no; 4717 : 33032 : + if (!is_binlog_name(name, &file_no)) 4718 : 29074 : + continue; 4719 : : + char full_path[OS_FILE_MAX_PATH]; 4720 : 3958 : + binlog_name_make(full_path, file_no); 4721 : 3958 : + if (my_delete(full_path, MYF(MY_WME))) 4722 : 0 : + err= true; 4723 : : + /* 4724 : : + Just as defensive coding, also remove any entry from the file hash 4725 : : + with this file_no. We would expect to have already deleted everything 4726 : : + in remove_up_to() above. 4727 : : + */ 4728 : 3958 : + ibb_file_hash.remove(file_no, lf_pins); 4729 : : + } 4730 : 1714 : + my_dirend(dir); 4731 : : + } 4732 : : + /* 4733 : : + If we get an error deleting any of the existing files, we report the error 4734 : : + back up. But we still try to initialize an empty binlog state, better than 4735 : : + leaving a non-functional binlog with corrupt internal state. 4736 : : + */ 4737 : : + 4738 : : + /* Re-initialize empty binlog state and start the pre-alloc thread. */ 4739 : 1714 : + innodb_binlog_init_state(); 4740 : 1714 : + load_global_binlog_state(&binlog_full_state); 4741 : 1714 : + ibb_pending_lsn_fifo.init(0); 4742 : 1714 : + binlog_page_fifo->unlock_with_delayed_free(); 4743 : 1714 : + start_binlog_prealloc_thread(); 4744 : 1714 : + binlog_sync_initial(); 4745 : : + 4746 : 1714 : + lf_hash_put_pins(lf_pins); 4747 : 1714 : + return err; 4748 : : +} 4749 : : + 4750 : : + 4751 : : +/* 4752 : : + Given a limit_file_no that is still needed by a slave (dump thread). 4753 : : + The dump thread will need to read any oob records references from event 4754 : : + groups in that file_no, so it will then also need to read from any earlier 4755 : : + file_no referenced from limit_file_no. 4756 : : + 4757 : : + This function handles this dependency, by reading the header page (or 4758 : : + getting from the ibb_file_hash if available) to get any earlier file_no 4759 : : + containing such references. 4760 : : +*/ 4761 : : +static bool 4762 : 120 : +purge_adjust_limit_file_no(handler_binlog_purge_info *purge_info, LF_PINS *pins) 4763 : : +{ 4764 : 120 : + uint64_t limit_file_no= purge_info->limit_file_no; 4765 : 120 : + if (limit_file_no == ~(uint64_t)0) 4766 : 36 : + return false; 4767 : : + 4768 : : + uint64_t referenced_file_no; 4769 : 84 : + if (ibb_file_hash.get_oob_ref_file_no(limit_file_no, pins, 4770 : : + &referenced_file_no)) 4771 : 0 : + return true; 4772 : : + 4773 : 84 : + if (referenced_file_no < limit_file_no) 4774 : 36 : + purge_info->limit_file_no= referenced_file_no; 4775 : : + else 4776 : 48 : + ut_ad(referenced_file_no == limit_file_no || 4777 : : + referenced_file_no == ~(uint64_t)0); 4778 : : + 4779 : 84 : + return false; 4780 : : +} 4781 : : + 4782 : : + 4783 : : +/** 4784 : : + The low-level function handling binlog purge. 4785 : : + 4786 : : + How much to purge is determined by: 4787 : : + 4788 : : + 1. Lowest file_no that should not be purged. This is determined as the 4789 : : + minimum of: 4790 : : + 1a. active_binlog_file_no 4791 : : + 1b. first_open_binlog_file_no 4792 : : + 1c. Any file_no in use by an active dump thread 4793 : : + 1d. Any file_no containing oob data referenced by file_no from (1c) 4794 : : + 1e. Any file_no containing oob data referenced by an active transaction. 4795 : : + 1f. User specified file_no (from PURGE BINARY LOGS TO, if any). 4796 : : + 4797 : : + 2. Unix timestamp specifying the minimal value that should not be purged, 4798 : : + optional (used by PURGE BINARY LOGS BEFORE and --binlog-expire-log-seconds). 4799 : : + 4800 : : + 3. Maximum total size of binlogs, optional (from --max-binlog-total-size). 4801 : : + 4802 : : + Sets out_file_no to the earliest binlog file not purged. 4803 : : + Additionally returns: 4804 : : + 4805 : : + 0 Purged all files as requested. 4806 : : + 1 Some files were not purged due to being currently in-use (by binlog 4807 : : + writing or active dump threads). 4808 : : +*/ 4809 : : +static int 4810 : 120 : +innodb_binlog_purge_low(handler_binlog_purge_info *purge_info, 4811 : : + uint64_t limit_name_file_no, LF_PINS *lf_pins, 4812 : : + uint64_t *out_file_no) 4813 : : + noexcept 4814 : : +{ 4815 : 120 : + uint64_t limit_file_no= purge_info->limit_file_no; 4816 : 120 : + bool by_date= purge_info->purge_by_date; 4817 : 120 : + bool by_size= purge_info->purge_by_size; 4818 : 120 : + bool by_name= purge_info->purge_by_name; 4819 : 120 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 4820 : 120 : + bool need_active_flush= (active <= limit_file_no + 2); 4821 : 120 : + ut_ad(by_date || by_size || by_name); 4822 : 120 : + ut_a(limit_file_no <= active); 4823 : 120 : + ut_a(limit_file_no <= first_open_binlog_file_no); 4824 : : + 4825 : 120 : + mysql_mutex_assert_owner(&purge_binlog_mutex); 4826 : 120 : + size_t loc_total_size= total_binlog_used_size; 4827 : : + uint64_t file_no; 4828 : : + bool want_purge; 4829 : : + 4830 : 120 : + for (file_no= earliest_binlog_file_no; ; ++file_no) 4831 : : + { 4832 : 266 : + want_purge= false; 4833 : : + 4834 : : + char filename[OS_FILE_MAX_PATH]; 4835 : 266 : + binlog_name_make(filename, file_no); 4836 : : + MY_STAT stat_buf; 4837 : 266 : + if (!my_stat(filename, &stat_buf, MYF(0))) 4838 : : + { 4839 : 0 : + if (my_errno == ENOENT) 4840 : 0 : + sql_print_information("InnoDB: File already gone when purging binlog " 4841 : : + "file '%s'", filename); 4842 : : + else 4843 : 0 : + sql_print_warning("InnoDB: Failed to stat() when trying to purge " 4844 : 0 : + "binlog file '%s' (errno: %d)", filename, my_errno); 4845 : 0 : + continue; 4846 : : + } 4847 : : + 4848 : 266 : + if (by_date && stat_buf.st_mtime < purge_info->limit_date) 4849 : 26 : + want_purge= true; 4850 : 266 : + if (by_size && loc_total_size > purge_info->limit_size) 4851 : 34 : + want_purge= true; 4852 : 266 : + if (by_name && file_no < limit_name_file_no) 4853 : 126 : + want_purge= true; 4854 : 186 : + if (!want_purge || 4855 : 452 : + file_no >= limit_file_no || 4856 : 156 : + ibb_file_hash.get_oob_ref_in_use(file_no, lf_pins)) 4857 : 120 : + break; 4858 : : + 4859 : 146 : + earliest_binlog_file_no= file_no + 1; 4860 : 146 : + if (loc_total_size < (size_t)stat_buf.st_size) 4861 : : + { 4862 : : + /* 4863 : : + Somehow we miscounted size, files changed from outside server or 4864 : : + possibly bug. We will handle not underflowing the total. If this 4865 : : + assertion becomes a problem for testing, it can just be removed. 4866 : : + */ 4867 : 0 : + ut_ad(0); 4868 : : + } 4869 : : + else 4870 : 146 : + loc_total_size-= (size_t)stat_buf.st_size; 4871 : : + 4872 : : + /* 4873 : : + Make sure that we always leave at least one binlog file durably non-empty, 4874 : : + by fsync()'ing the first page of the active file before deleting file 4875 : : + (active-2). This way, recovery will always have at least one file header 4876 : : + from which to determine the LSN at which to start applying redo records. 4877 : : + */ 4878 : 146 : + if (file_no + 2 >= active && need_active_flush) 4879 : : + { 4880 : 24 : + binlog_page_fifo->flush_up_to(active, 0); 4881 : 24 : + need_active_flush= false; 4882 : : + } 4883 : : + 4884 : 146 : + ibb_file_hash.remove(file_no, lf_pins); 4885 : 146 : + if (my_delete(filename, MYF(0))) 4886 : : + { 4887 : 0 : + if (my_errno == ENOENT) 4888 : : + { 4889 : : + /* 4890 : : + File already gone, just ignore the error. 4891 : : + (This should be somewhat unusual to happen as stat() succeeded). 4892 : : + */ 4893 : : + } 4894 : : + else 4895 : : + { 4896 : 0 : + sql_print_warning("InnoDB: Delete failed while trying to purge binlog " 4897 : 0 : + "file '%s' (errno: %d)", filename, my_errno); 4898 : 0 : + continue; 4899 : : + } 4900 : : + } 4901 : 146 : + } 4902 : 120 : + total_binlog_used_size= loc_total_size; 4903 : 120 : + *out_file_no= file_no; 4904 : 120 : + return (want_purge ? 1 : 0); 4905 : : +} 4906 : : + 4907 : : + 4908 : : +static void 4909 : 5086 : +innodb_binlog_autopurge(uint64_t first_open_file_no, LF_PINS *pins) 4910 : : +{ 4911 : : + handler_binlog_purge_info purge_info; 4912 : : +#ifdef HAVE_REPLICATION 4913 : : + extern bool ha_binlog_purge_info(handler_binlog_purge_info *out_info); 4914 : 5086 : + bool can_purge= ha_binlog_purge_info(&purge_info); 4915 : : +#else 4916 : : + bool can_purge= false; 4917 : : + memset(&purge_info, 0, sizeof(purge_info)); /* Silence compiler warnings. */ 4918 : : +#endif 4919 : 5086 : + if (!can_purge || 4920 : 780 : + !(purge_info.purge_by_size || purge_info.purge_by_date)) 4921 : 5054 : + return; 4922 : : + 4923 : : + /* 4924 : : + Do not purge the active file_no, nor any oob references out of the active 4925 : : + (the latter might be needed to recover the GTID state after server 4926 : : + restart). 4927 : : + */ 4928 : 32 : + uint64_t active= active_binlog_file_no.load(std::memory_order_relaxed); 4929 : 32 : + if (purge_info.limit_file_no > active) 4930 : 32 : + purge_info.limit_file_no= active; 4931 : : + 4932 : 32 : + if (purge_adjust_limit_file_no(&purge_info, pins)) 4933 : 0 : + return; 4934 : : + 4935 : : + /* Don't purge any actively open tablespace files. */ 4936 : 32 : + uint64_t orig_limit_file_no= purge_info.limit_file_no; 4937 : 32 : + if (purge_info.limit_file_no == ~(uint64_t)0 || 4938 : 32 : + purge_info.limit_file_no > first_open_file_no) 4939 : 0 : + purge_info.limit_file_no= first_open_file_no; 4940 : 32 : + purge_info.purge_by_name= false; 4941 : : + 4942 : : + uint64_t file_no; 4943 : 32 : + int res= innodb_binlog_purge_low(&purge_info, 0, pins, &file_no); 4944 : 32 : + if (res) 4945 : : + { 4946 : 2 : + if (!purge_warning_given) 4947 : : + { 4948 : : + char filename[BINLOG_NAME_MAX_LEN]; 4949 : 2 : + binlog_name_make_short(filename, file_no); 4950 : 2 : + if (purge_info.nonpurge_reason) 4951 : 0 : + sql_print_information("InnoDB: Binlog file %s could not be purged " 4952 : : + "because %s", 4953 : : + filename, purge_info.nonpurge_reason); 4954 : 2 : + else if (orig_limit_file_no == file_no) 4955 : 2 : + sql_print_information("InnoDB: Binlog file %s could not be purged " 4956 : : + "because it is in use by a binlog dump thread " 4957 : : + "(connected slave)", filename); 4958 : 0 : + else if (purge_info.limit_file_no == file_no) 4959 : 0 : + sql_print_information("InnoDB: Binlog file %s could not be purged " 4960 : : + "because it is in active use", filename); 4961 : : + else 4962 : 0 : + sql_print_information("InnoDB: Binlog file %s could not be purged " 4963 : : + "because it might still be needed", filename); 4964 : 2 : + purge_warning_given= true; 4965 : : + } 4966 : : + } 4967 : : + else 4968 : 30 : + purge_warning_given= false; 4969 : : +} 4970 : : + 4971 : : + 4972 : : +int 4973 : 90 : +innodb_binlog_purge(handler_binlog_purge_info *purge_info) 4974 : : +{ 4975 : : + /* 4976 : : + Let us check that we do not get an attempt to purge by file, date, and/or 4977 : : + size at the same time. 4978 : : + (If we do, it is not necesarily a problem, but this cannot happen in 4979 : : + current server code). 4980 : : + */ 4981 : 90 : + ut_ad(1 == (!!purge_info->purge_by_name + 4982 : : + !!purge_info->purge_by_date + 4983 : : + !!purge_info->purge_by_size)); 4984 : : + 4985 : 90 : + if (!purge_info->purge_by_name && !purge_info->purge_by_date && 4986 : 6 : + !purge_info->purge_by_size) 4987 : 0 : + return 0; 4988 : : + 4989 : 90 : + mysql_mutex_lock(&active_binlog_mutex); 4990 : : + uint64_t limit_file_no= 4991 : 90 : + std::min(active_binlog_file_no.load(std::memory_order_relaxed), 4992 : 90 : + first_open_binlog_file_no); 4993 : 90 : + uint64_t last_created= last_created_binlog_file_no; 4994 : 90 : + mysql_mutex_unlock(&active_binlog_mutex); 4995 : : + 4996 : 90 : + uint64_t to_file_no= ~(uint64_t)0; 4997 : 90 : + if (purge_info->purge_by_name) 4998 : : + { 4999 : 164 : + if (!is_binlog_name(purge_info->limit_name, &to_file_no) || 5000 : 82 : + to_file_no > last_created) 5001 : 2 : + return LOG_INFO_EOF; 5002 : : + } 5003 : : + 5004 : 88 : + LF_PINS *lf_pins= lf_hash_get_pins(&ibb_file_hash.hash); 5005 : 88 : + ut_a(lf_pins); 5006 : 88 : + if (purge_adjust_limit_file_no(purge_info, lf_pins)) 5007 : : + { 5008 : 0 : + lf_hash_put_pins(lf_pins); 5009 : 0 : + return LOG_INFO_IO; 5010 : : + } 5011 : : + 5012 : 88 : + uint64_t orig_limit_file_no= purge_info->limit_file_no; 5013 : 88 : + purge_info->limit_file_no= std::min(orig_limit_file_no, limit_file_no); 5014 : : + 5015 : 88 : + mysql_mutex_lock(&purge_binlog_mutex); 5016 : : + uint64_t file_no; 5017 : 88 : + int res= innodb_binlog_purge_low(purge_info, to_file_no, lf_pins, &file_no); 5018 : 88 : + mysql_mutex_unlock(&purge_binlog_mutex); 5019 : 88 : + lf_hash_put_pins(lf_pins); 5020 : : + 5021 : 88 : + if (res == 1) 5022 : : + { 5023 : : + static_assert(sizeof(purge_info->nonpurge_filename) >= BINLOG_NAME_MAX_LEN, 5024 : : + "No room to return filename"); 5025 : 38 : + binlog_name_make_short(purge_info->nonpurge_filename, file_no); 5026 : 38 : + if (!purge_info->nonpurge_reason) 5027 : : + { 5028 : 36 : + if (limit_file_no == file_no) 5029 : 8 : + purge_info->nonpurge_reason= "the binlog file is in active use"; 5030 : 28 : + else if (orig_limit_file_no == file_no) 5031 : 18 : + purge_info->nonpurge_reason= "it is in use by a binlog dump thread " 5032 : : + "(connected slave)"; 5033 : : + } 5034 : 38 : + res= LOG_INFO_IN_USE; 5035 : : + } 5036 : : + else 5037 : 50 : + purge_warning_given= false; 5038 : : + 5039 : 88 : + return res; 5040 : : +} 5041 : : + 5042 : : + 5043 : : +bool 5044 : 31268 : +binlog_recover_write_data(bool space_id, uint32_t page_no, 5045 : : + uint16_t offset, 5046 : : + lsn_t start_lsn, lsn_t lsn, 5047 : : + const byte *buf, size_t size) noexcept 5048 : : +{ 5049 : 31268 : + if (!recover_obj.inited) 5050 : 29 : + return recover_obj.init_recovery(space_id, page_no, offset, start_lsn, lsn, 5051 : 29 : + buf, size); 5052 : 31239 : + return recover_obj.apply_redo(space_id, page_no, offset, start_lsn, lsn, 5053 : 31239 : + buf, size); 5054 : : +} 5055 : : + 5056 : : + 5057 : : +void 5058 : 10620 : +binlog_recover_end(lsn_t lsn) noexcept 5059 : : +{ 5060 : 10620 : + if (recover_obj.inited) 5061 : 29 : + recover_obj.end_actions(true); 5062 : 10620 : +} ===== File: storage/innobase/include/buf0buf.h ===== 1509 : : size_t flush_list_requests; 1510 : : 1511 : 68665565 : TPOOL_SUPPRESS_TSAN void add_flush_list_requests(size_t size) 1512 : 68665565 : + { flush_list_requests+= size; } 1513 : : private: 1514 : : static constexpr unsigned PAGE_CLEANER_IDLE= 1; 1515 : : static constexpr unsigned FLUSH_LIST_ACTIVE= 2; ===== File: storage/innobase/include/fil0fil.h ===== 1142 : : NO_EXT = 0, 1143 : : IBD = 1, 1144 : : ISL = 2, 1145 : : + CFG = 3, 1146 : : + IBB = 4 1147 : : }; 1148 : : extern const char* dot_ext[]; 1149 : : #define DOT_IBD dot_ext[IBD] 1150 : : +#define DOT_IBB dot_ext[IBB] 1151 : : #define DOT_ISL dot_ext[ISL] 1152 : : #define DOT_CFG dot_ext[CFG] 1153 : : ===== File: storage/innobase/include/fsp0fsp.h ===== 333 : : dberr_t fsp_header_init(fil_space_t *space, uint32_t size, mtr_t *mtr) 334 : : MY_ATTRIBUTE((nonnull, warn_unused_result)); 335 : : 336 : : +buf_block_t* fsp_page_create(fil_space_t *space, uint32_t offset, 337 : : + mtr_t *mtr) noexcept; 338 : : + 339 : : /** Create a new segment. 340 : : @param space tablespace 341 : : @param byte_offset byte offset of the created segment header ===== File: storage/innobase/include/fsp0types.h ===== 27 : : #pragma once 28 : : #include "ut0byte.h" 29 : : 30 : : +/** All persistent tablespaces (except binlog tablespaces) have a smaller 31 : : +fil_space_t::id than this. */ 32 : : constexpr uint32_t SRV_SPACE_ID_UPPER_BOUND= 0xFFFFFFF0U; 33 : : /** The fil_space_t::id of the innodb_temporary tablespace. */ 34 : : constexpr uint32_t SRV_TMP_SPACE_ID= 0xFFFFFFFEU; ===== File: storage/innobase/include/fsp_binlog.h ===== 1 : : +/***************************************************************************** 2 : : + 3 : : +Copyright (c) 2024, Kristian Nielsen 4 : : + 5 : : +This program is free software; you can redistribute it and/or modify it under 6 : : +the terms of the GNU General Public License as published by the Free Software 7 : : +Foundation; version 2 of the License. 8 : : + 9 : : +This program is distributed in the hope that it will be useful, but WITHOUT 10 : : +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 : : +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 : : + 13 : : +You should have received a copy of the GNU General Public License along with 14 : : +this program; if not, write to the Free Software Foundation, Inc., 15 : : +51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA 16 : : + 17 : : +*****************************************************************************/ 18 : : + 19 : : +/**************************************************//** 20 : : +@file include/fsp_binlog.h 21 : : +InnoDB implementation of binlog. 22 : : +*******************************************************/ 23 : : + 24 : : +#ifndef fsp_binlog_h 25 : : +#define fsp_binlog_h 26 : : + 27 : : +#include 28 : : +#include 29 : : + 30 : : +#include "lf.h" 31 : : + 32 : : +#include "univ.i" 33 : : +#include "mtr0mtr.h" 34 : : + 35 : : + 36 : : +struct chunk_data_base; 37 : : +struct binlog_header_data; 38 : : + 39 : : +/* 4-byte "magic" identifying InnoDB binlog file (little endian). */ 40 : : +static constexpr uint32_t IBB_MAGIC= 0x010dfefe; 41 : : +static constexpr uint32_t IBB_FILE_VERS_MAJOR= 1; 42 : : +static constexpr uint32_t IBB_FILE_VERS_MINOR= 0; 43 : : + 44 : : +/** 45 : : + The size of the header page that is stored in the first page of a file. 46 : : + This is the smallest page size that can be used in a backwards compatible 47 : : + way. Having a fixed-size small header page means we can get the real page 48 : : + size of the file from the header page, but still be able to checksum the 49 : : + header page without relying on unchecked page size field to compute the 50 : : + checksum. 51 : : + 52 : : + (The remainder of the header page is just unused or could potentially 53 : : + later be used for other data as needed). 54 : : +*/ 55 : : +static constexpr uint32_t IBB_HEADER_PAGE_SIZE= 512; 56 : : +static constexpr uint32_t IBB_PAGE_SIZE_MIN= IBB_HEADER_PAGE_SIZE; 57 : : +static constexpr uint32_t IBB_PAGE_SIZE_MAX= 65536; 58 : : + 59 : : +/** Store crc32 checksum at the end of the page */ 60 : : +#define BINLOG_PAGE_CHECKSUM 4 61 : : + 62 : : +#define BINLOG_PAGE_DATA 0 63 : : +#define BINLOG_PAGE_DATA_END BINLOG_PAGE_CHECKSUM 64 : : + 65 : : + 66 : : +enum fsp_binlog_chunk_types { 67 : : + /* Zero means no data, effectively EOF. */ 68 : : + FSP_BINLOG_TYPE_EMPTY= 0, 69 : : + /* A binlogged committed event group. */ 70 : : + FSP_BINLOG_TYPE_COMMIT= 1, 71 : : + /* A binlog GTID state record. */ 72 : : + FSP_BINLOG_TYPE_GTID_STATE= 2, 73 : : + /* Out-of-band event group data. */ 74 : : + FSP_BINLOG_TYPE_OOB_DATA= 3, 75 : : + /* Dummy record, use to fill remainder of page (eg. FLUSH BINARY LOGS). */ 76 : : + FSP_BINLOG_TYPE_DUMMY= 4, 77 : : + /* User XA record containing XID and OOB reference for XA PREPARE. */ 78 : : + FSP_BINLOG_TYPE_XA_PREPARE= 5, 79 : : + /* User XA record containing XID for XA COMMIT/ROLLBACK. */ 80 : : + FSP_BINLOG_TYPE_XA_COMPLETE= 6, 81 : : + /* Must be one more than the last type. */ 82 : : + FSP_BINLOG_TYPE_END, 83 : : + 84 : : + /* Padding data at end of page. */ 85 : : + FSP_BINLOG_TYPE_FILLER= 0xff 86 : : +}; 87 : : + 88 : : +/** 89 : : + Bit set on the chunk type for a continuation chunk, when data needs to be 90 : : + split across pages. 91 : : +*/ 92 : : +static constexpr uint32_t FSP_BINLOG_FLAG_BIT_CONT= 7; 93 : : +static constexpr uint32_t FSP_BINLOG_FLAG_CONT= (1 << FSP_BINLOG_FLAG_BIT_CONT); 94 : : +/** 95 : : + Bit set on the chunk type for the last chunk (no continuation chunks 96 : : + follow) 97 : : +*/ 98 : : +static constexpr uint32_t FSP_BINLOG_FLAG_BIT_LAST= 6; 99 : : +static constexpr uint32_t FSP_BINLOG_FLAG_LAST= (1 << FSP_BINLOG_FLAG_BIT_LAST); 100 : : +static constexpr uint32_t FSP_BINLOG_TYPE_MASK= 101 : : + ~(FSP_BINLOG_FLAG_CONT | FSP_BINLOG_FLAG_LAST); 102 : : + 103 : : +/* Flag bits for FSP_BINLOG_TYPE_XA_COMPLETE. */ 104 : : +static constexpr uint32_t IBB_FL_XA_TYPE_MASK= 0x1; 105 : : +static constexpr uint32_t IBB_FL_XA_TYPE_COMMIT= 0x0; 106 : : +static constexpr uint32_t IBB_FL_XA_TYPE_ROLLBACK= 0x1; 107 : : + 108 : : +/** 109 : : + These are the chunk types that are allowed to occur in the middle of 110 : : + another record. 111 : : +*/ 112 : : +static constexpr uint64_t ALLOWED_NESTED_RECORDS= 113 : : + /* GTID STATE at start of page can occur in the middle of other record. */ 114 : : + ((uint64_t)1 << FSP_BINLOG_TYPE_GTID_STATE) | 115 : : + /* DUMMY data at tablespace end can occur in the middle of other record. */ 116 : : + ((uint64_t)1 << FSP_BINLOG_TYPE_DUMMY) 117 : : + ; 118 : : +/* Ensure that all types fit in the ALLOWED_NESTED_RECORDS bitmask. */ 119 : : +static_assert(FSP_BINLOG_TYPE_END <= 8*sizeof(ALLOWED_NESTED_RECORDS), 120 : : + "Binlog types must be <64 to fit " 121 : : + "in ALLOWED_NESTED_RECORDS bitmask"); 122 : : + 123 : : + 124 : : +extern uint32_t ibb_page_size_shift; 125 : : +extern ulong ibb_page_size; 126 : : + 127 : : + 128 : : +/** 129 : : + The object representing a binlog page that is not yet flushed to disk. 130 : : + At the end of the object is an additionally allocated byte buffer of 131 : : + size ibb_page_size, ie. the page buffer containing the data in the page. 132 : : + 133 : : + The LATCHED count is the number of current writers and readers of the page 134 : : + (the page cannot be flushed and freed until this drops to zero). 135 : : + 136 : : + The flag LAST_PAGE is set for the very last page in a tablespace file, 137 : : + used to hold this page latched until the end of a mini-transaction. 138 : : + 139 : : + The flag COMPLETE is set when the writer has written the last byte of the 140 : : + page (a page cannot be freed until it is complete, and will normally not be 141 : : + flushed unless required for an InnoDB log checkpoint). 142 : : + 143 : : + The flag FLUSHED_CLEAN is set if a (partial) page has been flushed to disk, 144 : : + and cleared again by a writer when more data is added to the page. 145 : : +*/ 146 : : +struct fsp_binlog_page_entry { 147 : : + uint32_t latched; 148 : : + /* Flag set for the last page in a file. */ 149 : : + bool last_page; 150 : : + /* 151 : : + Flag set when the page has been filled, no more data will be added and 152 : : + it is safe to write out to disk and remove from the FIFO. 153 : : + */ 154 : : + bool complete; 155 : : + /* 156 : : + Flag set when the page is not yet complete, but all data added so far 157 : : + have been written out to the file. So the page should not be written 158 : : + again (until more data is added), but nor can it be removed from the 159 : : + FIFO yet. 160 : : + */ 161 : : + bool flushed_clean; 162 : : + /* 163 : : + Flag set when the page is not yet complete, but nevertheless waiting to be 164 : : + flushed to disk (eg. due to InnoDB checkpointing). Used to avoid waking up 165 : : + the flush thread on every release of a last partial page in the file 166 : : + when it is not needed. 167 : : + */ 168 : : + bool pending_flush; 169 : : + 170 : 418186 : + byte *page_buf() { return (byte *)this + sizeof(fsp_binlog_page_entry); } 171 : : +}; 172 : : + 173 : : + 174 : : +/** 175 : : + A page FIFO, as a lower-level alternative to the buffer pool used for full 176 : : + tablespaces. 177 : : + 178 : : + Since binlog files are written strictly append-only, we can simply add new 179 : : + pages at the end and flush them from the beginning. 180 : : + 181 : : + Some attempt is made to get reasonable scalability of the page fifo (even 182 : : + though it is still protected by a global mutex that could potentially be 183 : : + contended between writers and readers). The mutex is only held shortly; 184 : : + a "latch" count in each page marks when there are active readers or writers 185 : : + preventing page flush and free. Thus readers and writers can access a page 186 : : + concurrently. File write operations/syscalls are done outside of holding the 187 : : + mutex, and a freelist is used to likewise avoid most malloc/free. 188 : : +*/ 189 : : +class fsp_binlog_page_fifo { 190 : : +public: 191 : : + /* 192 : : + Allow at most 1/N of the pages in one binlog file will be kept in-memory 193 : : + on the free list of page buffers. 194 : : + */ 195 : : + static constexpr uint64_t MAX_FREE_BUFFERS_FRAC= 4; 196 : : + 197 : : + struct page_list { 198 : : + fsp_binlog_page_entry **entries; 199 : : + size_t allocated_entries; 200 : : + size_t used_entries; 201 : : + size_t first_entry; 202 : : + uint32_t first_page_no; 203 : : + uint32_t size_in_pages; 204 : : + File fh; 205 : : + 206 : 188799 : + fsp_binlog_page_entry *&entry_at(size_t idx) 207 : : + { 208 : 188799 : + idx+= first_entry; 209 : 188799 : + if (idx >= allocated_entries) 210 : 8 : + idx-= allocated_entries; 211 : 188799 : + ut_ad(idx < allocated_entries); 212 : 188799 : + return entries[idx]; 213 : : + } 214 : : + 215 : : + }; 216 : : +private: 217 : : + mysql_mutex_t m_mutex; 218 : : + pthread_cond_t m_cond; 219 : : + std::thread flush_thread_obj; 220 : : + 221 : : + /* 222 : : + The first_file_no is the first valid file in the fifo. The other entry in 223 : : + the fifo holds (first_file_no+1) if it is not empty. 224 : : + If first_file_no==~0, then there are no files in the fifo (initial state 225 : : + just after construction). 226 : : + */ 227 : : + uint64_t first_file_no; 228 : : + page_list fifos[2]; 229 : : + /* 230 : : + Free list for page objects, to avoid repeated aligned_alloc(). 231 : : + Each object is allocated as a byte array of size 232 : : + sizeof(fsp_binlog_page_entry) + ibb_page_size, holding the 233 : : + fsp_binlog_page_entry object and the page buffer just after it. 234 : : + When on the freelist, instead just the first sizeof(byte *) bytes store 235 : : + a simple `next' pointer. 236 : : + */ 237 : : + size_t free_buffers; 238 : : + byte *freelist; 239 : : + /* Temporary overflow of freelist, to be freed after mutex is unlocked. */ 240 : : + byte *to_free_list; 241 : : + bool flushing; 242 : : + bool flush_thread_started; 243 : : + bool flush_thread_end; 244 : : + 245 : : +private: 246 : : + fsp_binlog_page_entry *get_entry(uint64_t file_no, uint64_t page_no, 247 : : + uint32_t latch, bool completed, bool clean); 248 : : + void release_entry(uint64_t file_no, uint64_t page_no); 249 : : + 250 : : +public: 251 : : + fsp_binlog_page_fifo(); 252 : : + ~fsp_binlog_page_fifo(); 253 : : + void reset(); 254 : : + void start_flush_thread(); 255 : : + void stop_flush_thread(); 256 : : + void flush_thread_run(); 257 : : + void lock_wait_for_idle(); 258 : 430 : + void unlock() { mysql_mutex_unlock(&m_mutex); } 259 : : + void unlock_with_delayed_free(); 260 : : + void create_tablespace(uint64_t file_no, uint32_t size_in_pages, 261 : : + uint32_t init_page= ~(uint32_t)0, 262 : : + byte *partial_page= nullptr); 263 : : + void release_tablespace(uint64_t file_no); 264 : : + void free_page_list(uint64_t file_no); 265 : : + fsp_binlog_page_entry *create_page(uint64_t file_no, uint32_t page_no); 266 : : + fsp_binlog_page_entry *get_page(uint64_t file_no, uint32_t page_no); 267 : : + void release_page(fsp_binlog_page_entry *page); 268 : : + void release_page_mtr(fsp_binlog_page_entry *page, mtr_t *mtr); 269 : : + bool has_unflushed(uint64_t file_no); 270 : : + void flush_one_page(uint64_t file_no, bool force); 271 : : + void flush_up_to(uint64_t file_no, uint32_t page_no); 272 : : + void do_fdatasync(uint64_t file_no); 273 : : + File get_fh(uint64_t file_no); 274 : 54842 : + uint32_t size_in_pages(uint64_t file_no) { 275 : 54842 : + return fifos[file_no & 1].size_in_pages; 276 : : + } 277 : 420 : + void truncate_file_size(uint64_t file_no, uint32_t size_in_pages) 278 : : + { 279 : 420 : + fifos[file_no & 1].size_in_pages= size_in_pages; 280 : 420 : + } 281 : : +}; 282 : : + 283 : : + 284 : : +/** Structure of an entry in the hash of binlog tablespace files. */ 285 : : +struct ibb_tblspc_entry { 286 : : + uint64_t file_no; 287 : : + /* 288 : : + Active transactions/oob-event-groups that start in this binlog tablespace 289 : : + file (including any user XA). 290 : : + */ 291 : : + std::atomicoob_refs; 292 : : + /* Active XA transactions whose oob start in this binlog tablespace file. */ 293 : : + std::atomicxa_refs; 294 : : + /* 295 : : + The earliest file number that this binlog tablespace file has oob 296 : : + references into. 297 : : + (This is a conservative estimate, references may not actually exist in 298 : : + case their commit record went into a later file, or they ended up rolling 299 : : + back). 300 : : + Includes any XA oob records. 301 : : + */ 302 : : + std::atomicoob_ref_file_no; 303 : : + /* Earliest file number that we have XA references into. */ 304 : : + std::atomicxa_ref_file_no; 305 : : + 306 : : + ibb_tblspc_entry()= default; 307 : : + ~ibb_tblspc_entry()= default; 308 : : +}; 309 : : + 310 : : + 311 : : +/** 312 : : + Class keeping reference counts of oob records starting in different binlog 313 : : + tablespace files. 314 : : + Used to keep track of which files should not be purged because they contain 315 : : + oob (start) records that are still referenced by needed binlog tablespace 316 : : + files or by active transactions. 317 : : +*/ 318 : : +class ibb_file_oob_refs { 319 : : +public: 320 : : + /* Hash contains struct ibb_tblspc_entry keyed on file_no. */ 321 : : + LF_HASH hash; 322 : : + /* 323 : : + Earliest file_no with start oob records that are still referenced by active 324 : : + transactions / event groups. 325 : : + */ 326 : : + std::atomic earliest_oob_ref; 327 : : + /* 328 : : + Same, but restricted to those oob that constitute XA transactions. 329 : : + Thus, this may be larger than earliest_oob_ref or even ~(uint64_t)0 in 330 : : + case there are no active XA. 331 : : + */ 332 : : + std::atomic earliest_xa_ref; 333 : : + 334 : : +public: 335 : : + /* Init the hash empty. */ 336 : : + void init() noexcept; 337 : : + void destroy() noexcept; 338 : : + /* Delete an entry from the hash. */ 339 : : + void remove(uint64_t file_no, LF_PINS *pins); 340 : : + /* Delete all (consecutive) entries from file_no down. */ 341 : : + void remove_up_to(uint64_t file_no, LF_PINS *pins); 342 : : + /* 343 : : + Update an entry when an OOB record is started/completed. 344 : : + Returns the resulting refcount, or ~0 if entry not found. 345 : : + The return is the xa refcnt if do_xa==true, else the oob refcnt. 346 : : + */ 347 : : + uint64_t oob_ref_inc(uint64_t file_no, LF_PINS *pins, bool do_xa= false); 348 : : + uint64_t oob_ref_dec(uint64_t file_no, LF_PINS *pins, bool do_xa= false); 349 : : + /* Update earliest_oob_ref when refcount drops to zero. */ 350 : : + void do_zero_refcnt_action(uint64_t file_no, LF_PINS *pins, 351 : : + bool active_moving); 352 : : + /* Update the oob and xa file_no's active at start of this file_no. */ 353 : : + bool update_refs(uint64_t file_no, LF_PINS *pins, 354 : : + uint64_t oob_ref, uint64_t xa_ref); 355 : : + /* Update earliest_xa_ref when xa_refs changes 0->1 or 1->0. */ 356 : : + void update_earliest_xa_ref(uint64_t ref_file_no, LF_PINS *pins); 357 : : + /* Lookup the oob-referenced file_no from a file_no. */ 358 : : + bool get_oob_ref_file_no(uint64_t file_no, LF_PINS *pins, 359 : : + uint64_t *out_oob_ref_file_no); 360 : : + /* Check if file_no needed by active, not committed transaction. */ 361 : : + bool get_oob_ref_in_use(uint64_t file_no, LF_PINS *pins); 362 : : + /* Check if _any_ file_no is needed by active, not committed transactions. */ 363 : : + bool check_any_oob_ref_in_use(uint64_t start_file_no, uint64_t end_file_no, 364 : : + LF_PINS *lf_pins); 365 : : +}; 366 : : + 367 : : + 368 : : +class binlog_chunk_reader { 369 : : +public: 370 : : + enum chunk_reader_status { 371 : : + CHUNK_READER_ERROR= -1, 372 : : + CHUNK_READER_EOF= 0, 373 : : + CHUNK_READER_FOUND= 1 374 : : + }; 375 : : + 376 : : + /* 377 : : + Current state, can be obtained from save_pos() and later passed to 378 : : + restore_pos(). 379 : : + */ 380 : : + struct saved_position { 381 : : + /* Current position file. */ 382 : : + uint64_t file_no; 383 : : + /* The file_no of the start of a record, if in_record is true. */ 384 : : + uint64_t rec_start_file_no; 385 : : + /* Current position page. */ 386 : : + uint32_t page_no; 387 : : + /* Start of current chunk inside page. */ 388 : : + uint32_t in_page_offset; 389 : : + /* 390 : : + The length of the current chunk, once the chunk type has been read. 391 : : + If 0, it means the chunk type (and length) has not yet been read. 392 : : + */ 393 : : + uint32_t chunk_len; 394 : : + /* The read position inside the current chunk. */ 395 : : + uint32_t chunk_read_offset; 396 : : + byte chunk_type; 397 : : + /* When set, read will skip the current chunk, if any. */ 398 : : + bool skip_current; 399 : : + /* Set while we are in the middle of reading a record. */ 400 : : + bool in_record; 401 : : + } s; 402 : : + 403 : : + /* Amount of data in file, valid after fetch_current_page(). */ 404 : : + uint64_t cur_end_offset; 405 : : + /* Length of the currently open file, valid if cur_file_handle != -1. */ 406 : : + uint64_t cur_file_length; 407 : : + /* 408 : : + If different from ~0, stop (return EOF) when reaching the end of this file. 409 : : + This is used for SHOW BINLOG EVENTS, which has an old file-based interface, 410 : : + and wants to show the events in a single file. 411 : : + */ 412 : : + uint64_t stop_file_no; 413 : : + /* 414 : : + After fetch_current_page(), this points into either cur_block or 415 : : + page_buffer as appropriate. 416 : : + */ 417 : : + byte *page_ptr; 418 : : + /* Valid after fetch_current_page(), if page found in buffer pool. */ 419 : : + fsp_binlog_page_entry *cur_block; 420 : : + /* Buffer for reading a page directly from a tablespace file. */ 421 : : + byte *page_buffer; 422 : : + /* 423 : : + Points to either binlog_cur_durable_offset, for readers that should not 424 : : + see binlog data until it has become durable on disk; or 425 : : + binlog_cur_end_offset otherwise. 426 : : + */ 427 : : + std::atomic * const limit_offset; 428 : : + /* Open file handle to tablespace file_no, or -1. */ 429 : : + File cur_file_handle; 430 : : + /* 431 : : + Flag used to skip the rest of any partial chunk we might be starting in 432 : : + the middle of. 433 : : + */ 434 : : + bool skipping_partial; 435 : : + 436 : : + binlog_chunk_reader(std::atomic *limit_offset_); 437 : 3798 : + void set_page_buf(byte *in_page_buf) { page_buffer= in_page_buf; } 438 : : + ~binlog_chunk_reader(); 439 : : + 440 : : + /* Current type, or FSP_BINLOG_TYPE_FILLER if between records. */ 441 : 225289 : + byte cur_type() { return (byte)(s.chunk_type & FSP_BINLOG_TYPE_MASK); } 442 : : + bool cur_is_cont() { return (s.chunk_type & FSP_BINLOG_FLAG_CONT) != 0; } 443 : 612455 : + bool end_of_record() { return !s.in_record; } 444 : 156652 : + bool is_end_of_page() noexcept 445 : : + { 446 : 156652 : + return s.in_page_offset >= ibb_page_size - (BINLOG_PAGE_DATA_END + 3); 447 : : + } 448 : : + static int read_error_corruption(uint64_t file_no, uint64_t page_no, 449 : : + const char *msg); 450 : 0 : + int read_error_corruption(const char *msg) 451 : : + { 452 : 0 : + return read_error_corruption(s.file_no, s.page_no, msg); 453 : : + } 454 : : + enum chunk_reader_status fetch_current_page(); 455 : : + /* 456 : : + Try to read max_len bytes from a record into buffer. 457 : : + 458 : : + If multipage is true, will move across pages to read following 459 : : + continuation chunks, if any, to try and read max_len total bytes. Only if 460 : : + the record ends before max_len bytes is less amount of bytes returned. 461 : : + 462 : : + If multipage is false, will read as much is available on one page (up to 463 : : + max of max_len), and then return. 464 : : + 465 : : + Returns number of bytes read, or -1 for error. 466 : : + Returns 0 if the chunk_reader is pointing to start of a chunk at the end 467 : : + of the current binlog (ie. end-of-file). 468 : : + */ 469 : : + int read_data(byte *buffer, int max_len, bool multipage); 470 : : + /* 471 : : + Find a chunk boundary at or after specified offset in current page. Used 472 : : + for init_legacy_pos() to find a valid starting position. 473 : : + */ 474 : : + int find_offset_in_page(uint32_t off); 475 : : + /* Read the file header of current file_no. */ 476 : : + int get_file_header(binlog_header_data *out_header); 477 : : + 478 : : + /* Save current position, and restore it later. */ 479 : 13015 : + void save_pos(saved_position *out_pos) { *out_pos= s; } 480 : : + void restore_pos(saved_position *pos); 481 : : + void seek(uint64_t file_no, uint64_t offset); 482 : : + 483 : : + /* 484 : : + Make next read_data() skip any data from the current chunk (if any), and 485 : : + start reading data only from the beginning of the next chunk. */ 486 : 37663 : + void skip_current() { if (s.in_record) s.skip_current= true; } 487 : : + /* 488 : : + Used initially, after seeking potentially into the middle of a (commit) 489 : : + record, to skip any continuation chunks until we reach the start of the 490 : : + first real record. 491 : : + */ 492 : 161456 : + void skip_partial(bool skip) { skipping_partial= skip; } 493 : : + /* Release any buffer pool page latch. */ 494 : : + void release(bool release_file_page= false); 495 : : + bool data_available(); 496 : : + bool is_before_pos(uint64_t file_no, uint64_t offset); 497 : 55421 : + uint64_t current_file_no() { return s.file_no; } 498 : 52739 : + uint64_t current_pos() { 499 : 52739 : + return (s.page_no << ibb_page_size_shift) + s.in_page_offset; 500 : : + } 501 : : +}; 502 : : + 503 : : + 504 : : +/** The state interval (in pages) used for active_binlog_file_no. */ 505 : : +extern uint64_t current_binlog_state_interval; 506 : : +extern mysql_mutex_t active_binlog_mutex; 507 : : +extern pthread_cond_t active_binlog_cond; 508 : : +extern mysql_mutex_t binlog_durable_mutex; 509 : : +extern mysql_cond_t binlog_durable_cond; 510 : : +extern std::atomic active_binlog_file_no; 511 : : +extern uint64_t first_open_binlog_file_no; 512 : : +extern uint64_t last_created_binlog_file_no; 513 : : +extern std::atomic binlog_cur_durable_offset[4]; 514 : : +extern std::atomic binlog_cur_end_offset[4]; 515 : : +extern fsp_binlog_page_fifo *binlog_page_fifo; 516 : : + 517 : : +extern ibb_file_oob_refs ibb_file_hash; 518 : : + 519 : : + 520 : : +static inline void 521 : 9514 : +fsp_binlog_release(fsp_binlog_page_entry *page) 522 : : +{ 523 : 9514 : + binlog_page_fifo->release_page(page); 524 : 9514 : +} 525 : : + 526 : : +extern size_t crc32_pwrite_page(File fd, byte *buf, uint32_t page_no, 527 : : + myf MyFlags) noexcept; 528 : : +extern int crc32_pread_page(File fd, byte *buf, uint32_t page_no, 529 : : + myf MyFlags) noexcept; 530 : : +extern int crc32_pread_page(pfs_os_file_t fh, byte *buf, uint32_t page_no, 531 : : + myf MyFlags) noexcept; 532 : : +extern bool ibb_record_in_file_hash(uint64_t file_no, uint64_t oob_ref, 533 : : + uint64_t xa_ref, LF_PINS *in_pins=nullptr); 534 : : +extern void binlog_write_up_to_now() noexcept; 535 : : +extern void fsp_binlog_extract_header_page(const byte *page_buf, 536 : : + binlog_header_data *out_header_data) 537 : : + noexcept; 538 : : +extern void fsp_log_binlog_write(mtr_t *mtr, fsp_binlog_page_entry *page, 539 : : + uint64_t file_no, uint32_t page_no, 540 : : + uint32_t page_offset, uint32_t len); 541 : : +extern void fsp_log_header_page(mtr_t *mtr, fsp_binlog_page_entry *page, 542 : : + uint64_t file_no, uint32_t len) noexcept; 543 : : +extern dberr_t fsp_binlog_init(); 544 : : +extern void fsp_binlog_shutdown(); 545 : : +extern dberr_t fsp_binlog_tablespace_close(uint64_t file_no); 546 : : +extern bool fsp_binlog_open(const char *file_name, pfs_os_file_t fh, 547 : : + uint64_t file_no, size_t file_size, 548 : : + uint32_t init_page, byte *partial_page); 549 : : +extern dberr_t fsp_binlog_tablespace_create(uint64_t file_no, 550 : : + uint32_t size_in_pages, 551 : : + LF_PINS *pins); 552 : : +extern std::pair fsp_binlog_write_rec( 553 : : + struct chunk_data_base *chunk_data, mtr_t *mtr, byte chunk_type, 554 : : + LF_PINS *pins); 555 : : +extern bool fsp_binlog_flush(); 556 : : + 557 : : +#endif /* fsp_binlog_h */ ===== File: storage/innobase/include/innodb_binlog.h ===== 1 : : +/***************************************************************************** 2 : : + 3 : : +Copyright (c) 2024, Kristian Nielsen 4 : : + 5 : : +This program is free software; you can redistribute it and/or modify it under 6 : : +the terms of the GNU General Public License as published by the Free Software 7 : : +Foundation; version 2 of the License. 8 : : + 9 : : +This program is distributed in the hope that it will be useful, but WITHOUT 10 : : +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 11 : : +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 : : + 13 : : +You should have received a copy of the GNU General Public License along with 14 : : +this program; if not, write to the Free Software Foundation, Inc., 15 : : +51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA 16 : : + 17 : : +*****************************************************************************/ 18 : : + 19 : : +/**************************************************//** 20 : : +@file include/innodb_binlog.h 21 : : +InnoDB implementation of binlog. 22 : : +*******************************************************/ 23 : : + 24 : : +#ifndef innodb_binlog_h 25 : : +#define innodb_binlog_h 26 : : + 27 : : +#include "univ.i" 28 : : +#include "fsp_binlog.h" 29 : : + 30 : : + 31 : : +struct mtr_t; 32 : : +struct rpl_binlog_state_base; 33 : : +struct rpl_gtid; 34 : : +struct handler_binlog_event_group_info; 35 : : +class handler_binlog_reader; 36 : : +struct handler_binlog_purge_info; 37 : : +struct binlog_oob_context; 38 : : + 39 : : + 40 : : +/** 41 : : + The struct chunk_data_base is a simple encapsulation of data for a chunk that 42 : : + is to be written to the binlog. Used to separate the generic code that 43 : : + handles binlog writing with page format and so on, from the details of the 44 : : + data being written, avoiding an intermediary buffer holding consecutive data. 45 : : + 46 : : + Currently used for: 47 : : + - chunk_data_cache: A binlog trx cache to be binlogged as a commit record. 48 : : + - chunk_data_oob: An out-of-band piece of event group data. 49 : : + - chunk_data_flush: For dummy filler data. 50 : : +*/ 51 : : +struct chunk_data_base { 52 : : + /* 53 : : + Copy at most max_len bytes to address p. 54 : : + Returns a pair with amount copied, and a bool if this is the last data. 55 : : + Should return the maximum amount of data available (up to max_len). Thus 56 : : + the size returned should only be less than max_len if the last-data flag 57 : : + is returned as true. 58 : : + */ 59 : : + virtual std::pair copy_data(byte *p, uint32_t max_len) = 0; 60 : 158687 : + virtual ~chunk_data_base() {}; 61 : : +}; 62 : : + 63 : : + 64 : : +/** 65 : : + Empty chunk data, used to pass a dummy record to fsp_binlog_write_rec() 66 : : + in fsp_binlog_flush(). 67 : : +*/ 68 : : +struct chunk_data_flush : public chunk_data_base { 69 : 2266 : + ~chunk_data_flush() { } 70 : : + 71 : 430 : + virtual std::pair copy_data(byte *p, uint32_t max_len) final 72 : : + { 73 : 430 : + memset(p, 0xff, max_len); 74 : 430 : + return {max_len, true}; 75 : : + } 76 : : +}; 77 : : + 78 : : + 79 : : +static constexpr size_t IBB_BINLOG_HEADER_SIZE= 64; 80 : : + 81 : : +/** 82 : : + Data stored at the start of each binlog file. 83 : : + (The data is stored as little-engian values in the first page of the file; 84 : : + this is just a struct to pass around the values in-memory). 85 : : +*/ 86 : : +struct binlog_header_data { 87 : : + /* 88 : : + The LSN corresponding to the start of the binlog file. Any redo record 89 : : + with smaller start (or end) LSN than this should be ignored during recovery 90 : : + and not applied to this file. 91 : : + */ 92 : : + lsn_t start_lsn; 93 : : + /* 94 : : + The file_no of the binlog file. This is written into the header to be able 95 : : + to recover it in the case where no binlog files are present at server 96 : : + start (could be due to FLUSH BINARY LOGS or RESET MASTER). 97 : : + */ 98 : : + uint64_t file_no; 99 : : + /* The length of this binlog file, in pages. */ 100 : : + uint64_t page_count; 101 : : + /* 102 : : + The interval (in pages) at which the (differential) binlog GTID state is 103 : : + written into the binlog file, for faster GTID position search. This 104 : : + corresponds to the value of --innodb-binlog-state-interval at the time the 105 : : + binlog file was created. 106 : : + */ 107 : : + uint64_t diff_state_interval; 108 : : + /* The earliest file_no that we have oob references into. */ 109 : : + uint64_t oob_ref_file_no; 110 : : + /* The earliest file_no that we have XA oob references into. */ 111 : : + uint64_t xa_ref_file_no; 112 : : + /* The log_2 of the page size (eg. ibb_page_size_shift). */ 113 : : + uint32_t page_size_shift; 114 : : + /* 115 : : + Major and minor file format version number. The idea is that minor version 116 : : + increments are backwards compatible, major version upgrades are not. 117 : : + */ 118 : : + uint32_t vers_major, vers_minor; 119 : : + /* Whether the page was found empty. */ 120 : : + bool is_empty; 121 : : + /* 122 : : + Whether the page was found invalid, bad magic or major version, or CRC32 123 : : + error (and not empty). 124 : : + */ 125 : : + bool is_invalid; 126 : : +}; 127 : : + 128 : : + 129 : : +/** 130 : : + The class pending_lsn_fifo keeps track of pending LSNs - and their 131 : : + corresponding binlog file_no/offset - that have been mtr-committed, but have 132 : : + not yet become durable. 133 : : + 134 : : + Used to delay sending to slaves any data that might be lost in case the 135 : : + master crashes just after sending. 136 : : +*/ 137 : : +class pending_lsn_fifo { 138 : : + static constexpr uint32_t fixed_size_log2= 10; 139 : : + static constexpr uint32_t fixed_size= (1 << fixed_size_log2); 140 : : + static constexpr uint32_t mask= (1 << fixed_size_log2) - 1; 141 : : +public: 142 : : + struct entry { 143 : : + lsn_t lsn; 144 : : + uint64_t file_no; 145 : : + uint64_t offset; 146 : : + } fifo[fixed_size]; 147 : : + /* 148 : : + Set while we are duing a durable sync of the redo log to the LSN that we 149 : : + are requesting to become durable. Used to avoid multiple threads 150 : : + needlessly trying to sync the redo log on top of one another. 151 : : + */ 152 : : + lsn_t flushing_lsn; 153 : : + /* 154 : : + The last added (and thus largest) lsn. Equal to cur_head().lsn when the 155 : : + fifo is not empty (and the lsn of the previous head when it is empty). 156 : : + */ 157 : : + lsn_t last_lsn_added; 158 : : + /* 159 : : + The current file_no that has any durable data. Used to detect when an LSN 160 : : + moves the current durable end point to the next file, so that the previous 161 : : + file can then be marked as fully durable. 162 : : + The value ~0 is used as a marker for "not yet initialized". 163 : : + */ 164 : : + uint64_t cur_file_no; 165 : : + /* The `head' points one past the most recent element. */ 166 : : + uint32_t head; 167 : : + /* The `tail' points to the earliest element. */ 168 : : + uint32_t tail; 169 : : + 170 : : + pending_lsn_fifo(); 171 : : + void init(uint64_t start_file_no); 172 : : + void reset(); 173 : 1015610 : + bool is_empty() { return head == tail; } 174 : 309936 : + bool is_full() { return head == tail + fixed_size; } 175 : 173531 : + entry &cur_head() { ut_ad(!is_empty()); return fifo[(head - 1) & mask]; } 176 : 178565 : + entry &cur_tail() { ut_ad(!is_empty()); return fifo[tail & mask]; } 177 : 154968 : + void drop_tail() { ut_ad(!is_empty()); ++tail; } 178 : 154968 : + void new_head() { ut_ad(!is_full()); ++head; } 179 : : + void record_commit(binlog_oob_context *c); 180 : : + void add_to_fifo(uint64_t lsn, uint64_t file_no, uint64_t offset); 181 : : + bool process_durable_lsn(lsn_t lsn); 182 : : +}; 183 : : + 184 : : + 185 : : +/** 186 : : + Class that keeps track of the oob references etc. for each 187 : : + XA PREPAREd XID. 188 : : +*/ 189 : : +class ibb_xid_hash { 190 : : +public: 191 : : + struct xid_elem { 192 : : + XID xid; 193 : : + uint64_t refcnt_file_no; 194 : : + uint64_t oob_num_nodes; 195 : : + uint64_t oob_first_file_no; 196 : : + uint64_t oob_first_offset; 197 : : + uint64_t oob_last_file_no; 198 : : + uint64_t oob_last_offset; 199 : : + }; 200 : : + HASH xid_hash; 201 : : + mysql_mutex_t xid_mutex; 202 : : + 203 : : + ibb_xid_hash(); 204 : : + ~ibb_xid_hash(); 205 : : + bool add_xid(const XID *xid, const binlog_oob_context *c); 206 : : + bool add_xid(const XID *xid, uint64_t refcnt_file_no, LF_PINS *pins, 207 : : + uint64_t num_nodes, 208 : : + uint64_t first_file_no, uint64_t first_offset, 209 : : + uint64_t last_file_no, uint64_t last_offset); 210 : : + xid_elem *grab_xid(const XID *xid); 211 : : + template bool run_on_xid(const XID *xid, F callback); 212 : : +}; 213 : : + 214 : : + 215 : : +#define BINLOG_NAME_BASE "binlog-" 216 : : +#define BINLOG_NAME_EXT ".ibb" 217 : : +/* '/' + "binlog-" + (<=20 digits) + '.' + "ibb" + '\0'. */ 218 : : +#define BINLOG_NAME_MAX_LEN 1 + 1 + 7 + 20 + 1 + 3 + 1 219 : : + 220 : : + 221 : : +extern pending_lsn_fifo ibb_pending_lsn_fifo; 222 : : +extern uint32_t innodb_binlog_size_in_pages; 223 : : +extern const char *innodb_binlog_directory; 224 : : +extern uint32_t binlog_cur_page_no; 225 : : +extern uint32_t binlog_cur_page_offset; 226 : : +extern ulonglong innodb_binlog_state_interval; 227 : : +extern rpl_binlog_state_base binlog_full_state; 228 : : +extern rpl_binlog_state_base binlog_diff_state; 229 : : +extern mysql_mutex_t purge_binlog_mutex; 230 : : +extern size_t total_binlog_used_size; 231 : : +extern ibb_xid_hash *ibb_xa_xid_hash; 232 : : + 233 : : + 234 : : +static inline void 235 : 24119 : +binlog_name_make(char name_buf[OS_FILE_MAX_PATH], uint64_t file_no, 236 : : + const char *binlog_dir) 237 : : +{ 238 : 24119 : + snprintf(name_buf, OS_FILE_MAX_PATH, 239 : : + "%s/" BINLOG_NAME_BASE "%06" PRIu64 BINLOG_NAME_EXT, 240 : : + binlog_dir, file_no); 241 : 24119 : +} 242 : : + 243 : : + 244 : : +static inline void 245 : 24015 : +binlog_name_make(char name_buf[OS_FILE_MAX_PATH], uint64_t file_no) 246 : : +{ 247 : 24015 : + binlog_name_make(name_buf, file_no, innodb_binlog_directory); 248 : 24013 : +} 249 : : + 250 : : + 251 : : +static inline void 252 : 22904 : +binlog_name_make_short(char *name_buf, uint64_t file_no) 253 : : +{ 254 : 22904 : + sprintf(name_buf, BINLOG_NAME_BASE "%06" PRIu64 BINLOG_NAME_EXT, file_no); 255 : 22904 : +} 256 : : + 257 : : + 258 : : +extern bool is_binlog_name(const char *name, uint64_t *out_idx); 259 : : +extern int get_binlog_header(const char *binlog_path, byte *page_buf, 260 : : + lsn_t &out_lsn, bool &out_empty) noexcept; 261 : : +extern dberr_t innodb_binlog_startup_init(); 262 : : +extern void ibb_set_max_size(size_t binlog_size); 263 : : +extern bool innodb_binlog_init(size_t binlog_size, const char *directory, 264 : : + HASH *recovery_hash); 265 : : +extern void innodb_binlog_close(bool shutdown); 266 : : +extern bool ibb_write_header_page(mtr_t *mtr, uint64_t file_no, 267 : : + uint64_t file_size_in_pages, lsn_t start_lsn, 268 : : + uint64_t gtid_state_interval_in_pages, 269 : : + LF_PINS *pins); 270 : : +extern bool binlog_gtid_state(rpl_binlog_state_base *state, mtr_t *mtr, 271 : : + fsp_binlog_page_entry * &block, uint32_t &page_no, 272 : : + uint32_t &page_offset, uint64_t file_no); 273 : : +extern bool innodb_binlog_oob_ordered(THD *thd, const unsigned char *data, 274 : : + size_t data_len, void **engine_data, 275 : : + void **stm_start_data, 276 : : + void **savepoint_data); 277 : : +extern bool innodb_binlog_oob(THD *thd, const unsigned char *data, 278 : : + size_t data_len, void **engine_data); 279 : : +void ibb_savepoint_rollback(THD *thd, void **engine_data, 280 : : + void **stmt_start_data, void **savepoint_data); 281 : : +extern void innodb_reset_oob(void **engine_data); 282 : : +extern void innodb_free_oob(void *engine_data); 283 : : +extern handler_binlog_reader *innodb_get_binlog_reader(bool wait_durable); 284 : : +extern void ibb_wait_durable_offset(uint64_t file_no, uint64_t wait_offset); 285 : : +extern void ibb_get_filename(char name[FN_REFLEN], uint64_t file_no); 286 : : +extern binlog_oob_context *innodb_binlog_trx(trx_t *trx, mtr_t *mtr); 287 : : +extern void innodb_binlog_post_commit(mtr_t *mtr, binlog_oob_context *c); 288 : : +extern bool innobase_binlog_write_direct_ordered 289 : : + (IO_CACHE *cache, handler_binlog_event_group_info *binlog_info, 290 : : + const rpl_gtid *gtid); 291 : : +extern bool innobase_binlog_write_direct 292 : : + (IO_CACHE *cache, handler_binlog_event_group_info *binlog_info, 293 : : + const rpl_gtid *gtid); 294 : : +extern void ibb_group_commit(THD *thd, 295 : : + handler_binlog_event_group_info *binlog_info); 296 : : +extern bool ibb_write_xa_prepare_ordered(THD *thd, 297 : : + handler_binlog_event_group_info *binlog_info, 298 : : + uchar engine_count); 299 : : +extern bool ibb_write_xa_prepare(THD *thd, 300 : : + handler_binlog_event_group_info *binlog_info, 301 : : + uchar engine_count); 302 : : +extern bool ibb_xa_rollback_ordered(THD *thd, const XID *xid, 303 : : + void **engine_data); 304 : : +extern bool ibb_xa_rollback(THD *thd, const XID *xid, void **engine_data); 305 : : +extern void ibb_binlog_unlog(const XID *xid, void **engine_data); 306 : : +extern bool innodb_find_binlogs(uint64_t *out_first, uint64_t *out_last); 307 : : +extern void innodb_binlog_status(uint64_t *out_file_no, uint64_t *out_pos); 308 : : +extern bool innodb_binlog_get_init_state(rpl_binlog_state_base *out_state); 309 : : +extern bool innodb_reset_binlogs(); 310 : : +extern int innodb_binlog_purge(handler_binlog_purge_info *purge_info); 311 : : +extern bool binlog_recover_write_data(bool space_id, uint32_t page_no, 312 : : + uint16_t offset, 313 : : + lsn_t start_lsn, lsn_t lsn, 314 : : + const byte *buf, size_t size) noexcept; 315 : : +extern void binlog_recover_end(lsn_t lsn) noexcept; 316 : : + 317 : : +#endif /* innodb_binlog_h */ ===== File: storage/innobase/include/log0log.h ===== 102 : : #define LOG_HEADER_CREATOR_END 48 103 : : /* @} */ 104 : : 105 : : +/** Fake tablespace id for InnoDB-implemented binlog files. */ 106 : : +static constexpr uint32_t LOG_BINLOG_ID_0= SRV_SPACE_ID_UPPER_BOUND; 107 : : +static constexpr uint32_t LOG_BINLOG_ID_1= SRV_SPACE_ID_UPPER_BOUND + 1; 108 : : + 109 : : struct log_t; 110 : : 111 : : /** File abstraction */ ===== File: storage/innobase/include/log0recv.h ===== 333 : : void parse_page0(const page_id_t id, const byte *b, bool size, bool flags) 334 : : noexcept; 335 : : 336 : : + /** Pass a binlog recovery record to the binlog implementation. 337 : : + @param space_id binlog file identifier 338 : : + @paral l log record 339 : : + @param rlen record length 340 : : + @param page_no page modified by the record 341 : : + @param start_lsn LSN at start of record 342 : : + @param lsn LSN at end of record 343 : : + @return whether record was found corrupt */ 344 : : + bool parse_store_binlog(uint32_t space_id, const byte *l, uint32_t rlen, 345 : : + uint32_t page_no, lsn_t start_lsn, lsn_t lsn); 346 : : + 347 : : /** @return whether parse_store() needs to be invoked 348 : : @param space_id tablespace identifier */ 349 : : bool parse_store_if_exists(uint32_t space_id) const noexcept; ===== File: storage/innobase/include/mtr0log.h ===== 313 : : { 314 : : static_assert(!(type & 15) && type != RESERVED && 315 : : type <= FILE_CHECKPOINT, "invalid type"); 316 : 295277298 : + ut_ad(type >= FILE_CREATE || is_named_space(id.space()) || 317 : : + id.space() == LOG_BINLOG_ID_0 || 318 : : + id.space() == LOG_BINLOG_ID_1); 319 : 295273289 : ut_ad(!bpage || bpage->id() == id); 320 : 295270192 : + ut_ad(id < end_page_id || 321 : : + id.space() == LOG_BINLOG_ID_0 || 322 : : + id.space() == LOG_BINLOG_ID_1); 323 : 295269566 : constexpr bool have_len= type != INIT_PAGE && type != FREE_PAGE; 324 : 295269566 : constexpr bool have_offset= type == WRITE || type == MEMSET || 325 : : type == MEMMOVE; 327 : 4691074 : ut_ad(have_len || len == 0); 328 : 4691074 : ut_ad(have_len || !alloc); 329 : 161758856 : ut_ad(have_offset || offset == 0); 330 : 295269566 : + ut_ad(offset + len <= srv_page_size || 331 : : + (type == WRITE && id.space() >= LOG_BINLOG_ID_0)); 332 : : static_assert(MIN_4BYTE >= UNIV_PAGE_SIZE_MAX, "consistency"); 333 : 209503841 : ut_ad(type == FREE_PAGE || type == OPTION || (type == EXTENDED && !bpage) || 334 : : + (type == WRITE && id.space() >= LOG_BINLOG_ID_0) || 335 : : memo_contains_flagged(bpage, MTR_MEMO_MODIFY)); 336 : : size_t max_len; 337 : : if (!have_len) ===== File: storage/innobase/include/mtr0mtr.h ===== 31 : : #include "buf0buf.h" 32 : : #include "small_vector.h" 33 : : 34 : : +struct fsp_binlog_page_entry; 35 : : + 36 : : + 37 : : /** Start a mini-transaction. */ 38 : : #define mtr_start(m) (m)->start() 39 : : 650 : : /** Note that log_sys.latch is no longer being held exclusively. */ 651 : 147820 : void flag_wr_unlock() noexcept { ut_ad(m_latch_ex); m_latch_ex= false; } 652 : : 653 : : + /* Binlog page release at mtr commit. */ 654 : 9516 : + fsp_binlog_page_entry *get_binlog_page() { return m_binlog_page; } 655 : 9516 : + void set_binlog_page(fsp_binlog_page_entry *page) { m_binlog_page= page; } 656 : : + 657 : : private: 658 : : /** Handle any pages that were freed during the mini-transaction. */ 659 : : void process_freed_pages(); 735 : : @param size total size of the record 736 : : @return the log record payload after the encoded length */ 737 : : static const byte *parse_length(const byte *l, uint32_t *size) noexcept; 738 : : + 739 : : + /** Write binlog data 740 : : + @param page_id binlog file id and page number 741 : : + @param offset offset within the page 742 : : + @param buf data 743 : : + @param size size of data 744 : : + @return */ 745 : : + void write_binlog(page_id_t page_id, uint16_t offset, 746 : : + const void *buf, size_t size) noexcept; 747 : : + 748 : : private: 749 : : 750 : : /** Release all latches. */ 820 : : fil_space_t *m_freed_space= nullptr; 821 : : /** set of freed page ids */ 822 : : range_set *m_freed_pages= nullptr; 823 : : + /** Latched binlog page to release at mtr commit*/ 824 : : + fsp_binlog_page_entry *m_binlog_page; 825 : : }; ===== File: storage/innobase/include/small_vector.h ===== 19 : : #pragma once 20 : : /* A normally small vector, inspired by llvm::SmallVector */ 21 : : #include "my_global.h" 22 : : +#include "my_valgrind.h" 23 : : #include 24 : : #include 25 : : ===== File: storage/innobase/include/trx0trx.h ===== 830 : : rollback. */ 831 : : /** whether this is holding the prepare mutex */ 832 : : bool active_commit_ordered; 833 : : + /** whether innobase_xa_prepare() was done. */ 834 : : + bool active_prepare; 835 : : /*------------------------------*/ 836 : : bool flush_log_later;/* In 2PC, we hold the 837 : : prepare_commit mutex across ===== File: storage/innobase/include/univ.i ===== 481 : : extern mysql_pfs_key_t trx_pool_manager_mutex_key; 482 : : extern mysql_pfs_key_t lock_wait_mutex_key; 483 : : extern mysql_pfs_key_t srv_threads_mutex_key; 484 : : +extern mysql_pfs_key_t fsp_active_binlog_mutex_key; 485 : : +extern mysql_pfs_key_t fsp_binlog_durable_mutex_key; 486 : : +extern mysql_pfs_key_t fsp_binlog_durable_cond_key; 487 : : +extern mysql_pfs_key_t fsp_purge_binlog_mutex_key; 488 : : +extern mysql_pfs_key_t fsp_page_fifo_mutex_key; 489 : : +extern mysql_pfs_key_t ibb_xid_hash_mutex_key; 490 : : # endif /* UNIV_PFS_MUTEX */ 491 : : 492 : : # ifdef UNIV_PFS_RWLOCK ===== File: storage/innobase/include/ut0new.h ===== 165 : : happens then that means that the list of predefined names must be extended. 166 : : Keep this list alphabetically sorted. */ 167 : : extern PSI_memory_key mem_key_ahi; 168 : : +extern PSI_memory_key mem_key_binlog; 169 : : extern PSI_memory_key mem_key_buf_buf_pool; 170 : : extern PSI_memory_key mem_key_dict_stats_bg_recalc_pool_t; 171 : : extern PSI_memory_key mem_key_dict_stats_index_map_t; 850 : : "fil0crypt", 851 : : "fil0fil", 852 : : "fsp0file", 853 : : + "fsp_binlog", 854 : : "fts0ast", 855 : : "fts0blex", 856 : : "fts0config", 862 : : "fts0sql", 863 : : "fts0tlex", 864 : : "gis0sea", 865 : : + "innodb_binlog", 866 : : "ha_innodb", 867 : : "handler0alter", 868 : : "hash0hash", ===== File: storage/innobase/log/log0recv.cc ===== 54 : : #include "srv0srv.h" 55 : : #include "srv0start.h" 56 : : #include "fil0pagecompress.h" 57 : : +#include "innodb_binlog.h" 58 : : #include "log.h" 59 : : 60 : : /** The recovery system */ 2563 : 224645 : fil_space_set_recv_size_and_flags(space_id, s, f); 2564 : 224645 : } 2565 : : 2566 : : +ATTRIBUTE_COLD 2567 : : +ATTRIBUTE_NOINLINE 2568 : 31268 : +bool recv_sys_t::parse_store_binlog(uint32_t space_id, const byte *l, 2569 : : + uint32_t rlen, uint32_t page_no, 2570 : : + lsn_t start_lsn, lsn_t lsn) 2571 : : +{ 2572 : 31268 : + const size_t olen= mlog_decode_varint_length(*l); 2573 : 31268 : + if (UNIV_UNLIKELY(olen >= rlen) || UNIV_UNLIKELY(olen > 3)) 2574 : 0 : + return true; 2575 : 31268 : + const uint32_t offset= mlog_decode_varint(l); 2576 : 31268 : + ut_ad(offset != MLOG_DECODE_ERROR); 2577 : 31268 : + if (UNIV_UNLIKELY(offset + rlen - olen >= 65535)) 2578 : 0 : + return true; 2579 : 31268 : + if (binlog_recover_write_data(space_id & 1, page_no, uint16_t(offset), 2580 : 31268 : + start_lsn, lsn, l + olen, rlen - olen)) 2581 : 0 : + return true; 2582 : 31268 : + return false; 2583 : : +} 2584 : : + 2585 : : ATTRIBUTE_NOINLINE 2586 : 15622819 : bool recv_sys_t::parse_store_if_exists(uint32_t space_id) const noexcept 2587 : : { 2776 : : 2777 : 1112551 : if (space_id == TRX_SYS_SPACE || srv_is_undo_tablespace(space_id)) 2778 : 0 : goto file_rec_error; 2779 : 1112551 : + if (fnend - l < 4 || 2780 : 1112551 : + (memcmp(fnend - 4, DOT_IBD, 4) && memcmp(fnend - 4, DOT_IBB, 4))) 2781 : 0 : goto file_rec_error; 2782 : : 2783 : 1112551 : if (UNIV_UNLIKELY(!recv_needed_recovery && srv_read_only_mode)) 2829 : 38597762 : log_page_modify(uint32_t space_id, uint32_t page_no) noexcept 2830 : : { 2831 : 38597762 : ut_ad(space_id); 2832 : 38597762 : + if (space_id >= SRV_SPACE_ID_UPPER_BOUND || srv_is_undo_tablespace(space_id)) 2833 : 21760540 : return recv_sys_t::OK; 2834 : 16837222 : recv_spaces_t::iterator i= recv_spaces.lower_bound(space_id); 2835 : 16837222 : const lsn_t lsn{recv_sys.lsn}; 2914 : 10804 : return r; 2915 : : 2916 : 88214646 : l= mtr_t::parse_length(l, &rlen); 2917 : 88214646 : + bool is_binlog= false; 2918 : : uint32_t idlen; 2919 : 88214646 : if ((b & 0x80) && got_page_op) 2920 : : { 2950 : 59249455 : space_id= mlog_decode_varint(l); 2951 : 59249455 : if (UNIV_UNLIKELY(space_id == MLOG_DECODE_ERROR)) 2952 : 0 : goto page_id_corrupted; 2953 : : + static_assert((LOG_BINLOG_ID_0 | 1) == LOG_BINLOG_ID_1, ""); 2954 : 59249455 : + is_binlog= !ENC_10_8 && (space_id | 1) == LOG_BINLOG_ID_1; 2955 : 59249455 : l+= idlen; 2956 : 59249455 : rlen-= idlen; 2957 : 59249455 : idlen= mlog_decode_varint_length(*l); 3002 : : } 3003 : 15831975 : same_page: 3004 : 81509840 : if (!rlen); 3005 : 80159691 : + else if (!is_binlog && UNIV_UNLIKELY(size_t(l - recs) + rlen > srv_page_size)) 3006 : 0 : goto record_corrupted; 3007 : 81509840 : const page_id_t id{space_id, page_no}; 3008 : 81509840 : ut_d(if ((b & 0x70) == INIT_PAGE || (b & 0x70) == OPTION) 3092 : : /* fall through */ 3093 : : case RESERVED: 3094 : 4219816 : continue; 3095 : 13938992 : case MEMMOVE: 3096 : : case MEMSET: 3097 : 11534143 : + if (storing == YES && !ENC_10_8 && is_binlog) 3098 : 0 : + goto record_corrupted; 3099 : : + /* fall through */ 3100 : : + case WRITE: 3101 : : if (storing == BACKUP) 3102 : : continue; 3103 : 8156721 : + if (storing == NO && UNIV_LIKELY((page_no | (uint32_t)is_binlog) != 0)) 3104 : : /* fil_space_set_recv_size_and_flags() is mandatory for storing==NO. 3105 : : It is only applicable to page_no == 0. Other than that, we can just 3106 : : ignore the payload and only compute the mini-transaction checksum; 3110 : 0 : goto record_corrupted; 3111 : : if (ENC_10_8) 3112 : 0 : cl= log_decrypt_legacy(iv, recs, l, rlen, decrypt_buf); 3113 : 38936986 : + if (!is_binlog) 3114 : : + { 3115 : 38905718 : + const uint32_t olen= mlog_decode_varint_length(*(ENC_10_8 ? cl : l)); 3116 : 38905718 : + if (UNIV_UNLIKELY(olen >= rlen) || UNIV_UNLIKELY(olen > 3)) 3117 : 0 : + goto record_corrupted; 3118 : 38905718 : + const uint32_t offset= mlog_decode_varint(ENC_10_8 ? cl : l); 3119 : 38905718 : + ut_ad(offset != MLOG_DECODE_ERROR); 3120 : : + static_assert(FIL_PAGE_OFFSET == 4, "compatibility"); 3121 : 38905718 : + if (UNIV_UNLIKELY(offset >= srv_page_size)) 3122 : 0 : + goto record_corrupted; 3123 : 38905718 : + last_offset+= offset; 3124 : 38905718 : + if (UNIV_UNLIKELY(last_offset < 8 || last_offset >= srv_page_size)) 3125 : 0 : + goto record_corrupted; 3126 : 38905718 : + (ENC_10_8 ? cl : l)+= olen; 3127 : 38905718 : + rlen-= olen; 3128 : : + } 3129 : 38936986 : if ((b & 0x70) == WRITE) 3130 : : { 3131 : 27223264 : + if (!ENC_10_8 && is_binlog); 3132 : 27191996 : + else if (UNIV_UNLIKELY(rlen + last_offset > srv_page_size)) 3133 : 0 : goto record_corrupted; 3134 : 27191996 : + else if (UNIV_UNLIKELY(!page_no) && file_checkpoint) 3135 : : { 3136 : 4577541 : const bool has_size= last_offset <= FSP_HEADER_OFFSET + FSP_SIZE && 3137 : 297861 : last_offset + rlen >= FSP_HEADER_OFFSET + FSP_SIZE + 4; 3156 : 0 : goto record_corrupted; 3157 : 11713722 : const uint32_t len= mlog_decode_varint(ENC_10_8 ? cl : l); 3158 : 11713722 : ut_ad(len != MLOG_DECODE_ERROR); 3159 : 11713722 : + if (UNIV_UNLIKELY(last_offset + len > srv_page_size) || is_binlog) 3160 : 0 : goto record_corrupted; 3161 : 11713722 : (ENC_10_8 ? cl : l)+= llen; 3162 : 11713722 : rlen-= llen; 3193 : : } 3194 : : #endif 3195 : : if (storing != YES); 3196 : 65889222 : + else if (!ENC_10_8 && is_binlog) 3197 : : + { 3198 : 31268 : + if (parse_store_binlog(space_id, l, rlen, page_no, start_lsn, lsn)) 3199 : 0 : + goto record_corrupted; 3200 : : + } 3201 : 65857954 : else if (if_exists && !parse_store_if_exists(space_id)); 3202 : 60240399 : else if (UNIV_UNLIKELY(parse_store(id, ENC_10_8 && l != cl 3203 : : ? decrypt_buf : recs, 4419 : 169 : ut_ad(!rewound_lsn); 4420 : 169 : ut_ad(recv_sys.lsn >= recv_sys.file_checkpoint); 4421 : 169 : log_sys.set_recovered_lsn(recv_sys.lsn); 4422 : 169 : + binlog_recover_end(recv_sys.lsn); 4423 : : } 4424 : 10620 : else if (rewound_lsn) 4425 : : { 4427 : 169 : ut_ad(recv_sys.file_checkpoint); 4428 : 169 : recv_sys.lsn= rewound_lsn; 4429 : : } 4430 : 10451 : + else if (store) 4431 : 10451 : + binlog_recover_end(recv_sys.lsn); 4432 : : + 4433 : 0 : func_exit: 4434 : 10789 : ut_d(recv_sys.after_apply= last_phase); 4435 : 10789 : mysql_mutex_unlock(&recv_sys.mutex); ===== File: storage/innobase/mtr/mtr0mtr.cc ===== 35 : : #include "btr0cur.h" 36 : : #include "srv0start.h" 37 : : #include "trx0trx.h" 38 : : +#include "fsp_binlog.h" 39 : : #include "log.h" 40 : : #include "my_cpu.h" 41 : : 208 : 157551955 : m_user_space= nullptr; 209 : 157551955 : m_commit_lsn= 0; 210 : 157551955 : m_trim_pages= false; 211 : 157551955 : + m_binlog_page= nullptr; 212 : 157551955 : } 213 : : 214 : : /** Release the resources */ 218 : 157044287 : ut_ad(m_memo.empty()); 219 : 157043664 : m_log.erase(); 220 : 157027995 : ut_d(m_commit= true); 221 : 157027995 : + if (m_binlog_page) 222 : : + { 223 : 9514 : + fsp_binlog_release(m_binlog_page); 224 : 9514 : + m_binlog_page= nullptr; 225 : : + } 226 : 157027995 : } 227 : : 228 : : /** Handle any pages that were freed during the mini-transaction. */ ===== File: storage/innobase/os/os0file.cc ===== 1560 : 9429 : return success; 1561 : : } 1562 : : 1563 : 331048 : + DBUG_EXECUTE_IF( 1564 : : + "ib_alloc_file_disk_full", 1565 : : + errno = ENOSPC; 1566 : : + return(false); 1567 : : + ); 1568 : : + 1569 : : # ifdef HAVE_POSIX_FALLOCATE 1570 : : int err; 1571 : : os_offset_t current_size; ===== File: storage/innobase/srv/srv0start.cc ===== 87 : : #include "row0mysql.h" 88 : : #include "btr0pcur.h" 89 : : #include "ibuf0ibuf.h" 90 : : +#include "innodb_binlog.h" 91 : : #include "zlib.h" 92 : : #include "log.h" 93 : : 1969 : 0 : return(srv_init_abort(err)); 1970 : : } 1971 : : 1972 : 11023 : + err= innodb_binlog_startup_init(); 1973 : 11023 : + if (UNIV_UNLIKELY(err != DB_SUCCESS)) 1974 : : + { 1975 : 0 : + sql_print_error("InnoDB: Could not initialize the binlog in InnoDB, " 1976 : : + "aborting"); 1977 : 0 : + return(srv_init_abort(DB_ERROR)); 1978 : : + } 1979 : : + 1980 : 11023 : if (!srv_read_only_mode 1981 : 10947 : && srv_operation <= SRV_OPERATION_EXPORT_RESTORED) { 1982 : : /* Initialize the innodb_temporary tablespace and keep 2109 : 9653 : logs_empty_and_mark_files_at_shutdown(); 2110 : : } 2111 : : 2112 : 10669 : + innodb_binlog_close(true); 2113 : 10669 : os_aio_free(); 2114 : 10669 : fil_space_t::close_all(); 2115 : : /* Exit any remaining threads. */ ===== File: storage/innobase/trx/trx0trx.cc ===== 49 : : #include "trx0xa.h" 50 : : #include "ut0pool.h" 51 : : #include "ut0vec.h" 52 : : +#include "innodb_binlog.h" 53 : : #include "log.h" 54 : : 55 : : #include 115 : : 116 : 2208050 : trx->active_commit_ordered = false; 117 : : 118 : 2208050 : + trx->active_prepare = false; 119 : : + 120 : 2208050 : trx->isolation_level = TRX_ISO_REPEATABLE_READ; 121 : : 122 : 2208050 : trx->check_foreigns = true; 423 : : bulk_insert */); 424 : : MEM_NOACCESS(&is_registered, sizeof is_registered); 425 : : MEM_NOACCESS(&active_commit_ordered, sizeof active_commit_ordered); 426 : : + MEM_NOACCESS(&active_prepare, sizeof active_prepare); 427 : : MEM_NOACCESS(&flush_log_later, sizeof flush_log_later); 428 : : MEM_NOACCESS(&duplicates, sizeof duplicates); 429 : : MEM_NOACCESS(&dict_operation, sizeof dict_operation); 1137 : 1361702 : ut_ad(!read_only); 1138 : 1361702 : trx_rseg_t *rseg= rsegs.m_redo.rseg; 1139 : 1361702 : trx_undo_t *&undo= rsegs.m_redo.undo; 1140 : 1361702 : + binlog_oob_context *binlog_ctx= nullptr; 1141 : 1361702 : if (UNIV_LIKELY(undo != nullptr)) 1142 : : { 1143 : 1361702 : MONITOR_INC(MONITOR_TRX_COMMIT_UNDO); 1177 : : } 1178 : : else 1179 : 614367 : trx_sys.assign_new_trx_no(this); 1180 : : + 1181 : : + /* Include binlog data in the commit record, if any. */ 1182 : 1324728 : + if (active_commit_ordered) 1183 : 390981 : + binlog_ctx= innodb_binlog_trx(this, mtr); 1184 : : + 1185 : 1324728 : UT_LIST_REMOVE(rseg->undo_list, undo); 1186 : : /* Change the undo log segment state from TRX_UNDO_ACTIVE, to 1187 : : define the transaction as committed in the file based domain, 1195 : 0 : rseg->release(); 1196 : 1361706 : mtr->commit(); 1197 : 1361705 : commit_lsn= undo_no || !xid.is_null() ? mtr->commit_lsn() : 0; 1198 : 1361705 : + innodb_binlog_post_commit(mtr, binlog_ctx); 1199 : 1361704 : } 1200 : : 1201 : : /******************************************************************** 1754 : 25272 : case 0: 1755 : 25272 : return; 1756 : 769015 : case 1: 1757 : 769015 : + if (trx->active_commit_ordered && trx->active_prepare) 1758 : 0 : return; 1759 : : } 1760 : 818345 : trx_flush_log_if_needed(lsn, trx); ===== File: storage/innobase/ut/ut0new.cc ===== 38 : : #ifdef BTR_CUR_HASH_ADAPT 39 : : PSI_memory_key mem_key_ahi; 40 : : #endif /* BTR_CUR_HASH_ADAPT */ 41 : : +PSI_memory_key mem_key_binlog; 42 : : PSI_memory_key mem_key_buf_buf_pool; 43 : : PSI_memory_key mem_key_dict_stats_bg_recalc_pool_t; 44 : : PSI_memory_key mem_key_dict_stats_index_map_t; 66 : : #ifdef BTR_CUR_HASH_ADAPT 67 : : {&mem_key_ahi, "adaptive hash index", 0}, 68 : : #endif /* BTR_CUR_HASH_ADAPT */ 69 : : + {&mem_key_binlog, "innodb binlog implementation", 0}, 70 : : {&mem_key_buf_buf_pool, "buf_buf_pool", 0}, 71 : : {&mem_key_dict_stats_bg_recalc_pool_t, "dict_stats_bg_recalc_pool_t", 0}, 72 : : {&mem_key_dict_stats_index_map_t, "dict_stats_index_map_t", 0}, ===== File: support-files/magic ===== 24 : : >3 byte x Version %d 25 : : 0 belong&0xffffff00 0xfefe0b00 MariaDB DDL recovery log 26 : : >3 byte x Version %d 27 : : +0 belong&0xffffff00 0xfefe0c00 MariaDB GTID index file 28 : : +>3 byte x Version %d 29 : : +0 belong&0xffffff00 0xfefe0d01 MariaDB InnoDB new binlog format 30 : : +>3 byte x Version %d Line coverage: 14173/14984 Branch coverage: 0/0