1 /*****************************************************************************
2 
3 Copyright (c) 1996, 2017, Oracle and/or its affiliates. All Rights Reserved.
4 Copyright (c) 2017, 2021, MariaDB Corporation.
5 
6 This program is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free Software
8 Foundation; version 2 of the License.
9 
10 This program is distributed in the hope that it will be useful, but WITHOUT
11 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License along with
15 this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
17 
18 *****************************************************************************/
19 
20 /**************************************************//**
21 @file trx/trx0sys.cc
22 Transaction system
23 
24 Created 3/26/1996 Heikki Tuuri
25 *******************************************************/
26 
27 #include "trx0sys.h"
28 #include "mysqld.h"
29 #include "sql_error.h"
30 
31 #include "fsp0fsp.h"
32 #include "mtr0log.h"
33 #include "mtr0log.h"
34 #include "trx0trx.h"
35 #include "trx0rseg.h"
36 #include "trx0undo.h"
37 #include "srv0srv.h"
38 #include "srv0start.h"
39 #include "trx0purge.h"
40 #include "log0log.h"
41 #include "log0recv.h"
42 #include "os0file.h"
43 
44 /** The transaction system */
45 trx_sys_t		trx_sys;
46 
47 /** Check whether transaction id is valid.
48 @param[in]	id              transaction id to check
49 @param[in]      name            table name */
50 void
check_trx_id_sanity(trx_id_t id,const table_name_t & name)51 ReadViewBase::check_trx_id_sanity(
52 	trx_id_t		id,
53 	const table_name_t&	name)
54 {
55 	if (id >= trx_sys.get_max_trx_id()) {
56 
57 		ib::warn() << "A transaction id"
58 			   << " in a record of table "
59 			   << name
60 			   << " is newer than the"
61 			   << " system-wide maximum.";
62 		ut_ad(0);
63 		THD *thd = current_thd;
64 		if (thd != NULL) {
65 			char    table_name[MAX_FULL_NAME_LEN + 1];
66 
67 			innobase_format_name(
68 				table_name, sizeof(table_name),
69 				name.m_name);
70 
71 			push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
72 					    ER_SIGNAL_WARN,
73 					    "InnoDB: Transaction id"
74 					    " in a record of table"
75 					    " %s is newer than system-wide"
76 					    " maximum.", table_name);
77 		}
78 	}
79 }
80 
81 #ifdef UNIV_DEBUG
82 /* Flag to control TRX_RSEG_N_SLOTS behavior debugging. */
83 uint	trx_rseg_n_slots_debug = 0;
84 #endif
85 
86 /** Display the MySQL binlog offset info if it is present in the trx
87 system header. */
88 void
trx_sys_print_mysql_binlog_offset()89 trx_sys_print_mysql_binlog_offset()
90 {
91 	if (!*trx_sys.recovered_binlog_filename) {
92 		return;
93 	}
94 
95 	ib::info() << "Last binlog file '"
96 		<< trx_sys.recovered_binlog_filename
97 		<< "', position "
98 		<< trx_sys.recovered_binlog_offset;
99 }
100 
101 /** Find an available rollback segment.
102 @param[in]	sys_header
103 @return an unallocated rollback segment slot in the TRX_SYS header
104 @retval ULINT_UNDEFINED if not found */
105 ulint
trx_sys_rseg_find_free(const buf_block_t * sys_header)106 trx_sys_rseg_find_free(const buf_block_t* sys_header)
107 {
108 	for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) {
109 		if (trx_sysf_rseg_get_page_no(sys_header, rseg_id)
110 		    == FIL_NULL) {
111 			return rseg_id;
112 		}
113 	}
114 
115 	return(ULINT_UNDEFINED);
116 }
117 
118 /** Count the number of initialized persistent rollback segment slots. */
119 static
120 void
trx_sysf_get_n_rseg_slots()121 trx_sysf_get_n_rseg_slots()
122 {
123 	mtr_t		mtr;
124 	mtr.start();
125 
126 	srv_available_undo_logs = 0;
127 	if (const buf_block_t* sys_header = trx_sysf_get(&mtr, false)) {
128 		for (ulint rseg_id = 0; rseg_id < TRX_SYS_N_RSEGS; rseg_id++) {
129 			srv_available_undo_logs
130 				+= trx_sysf_rseg_get_page_no(sys_header,
131 							     rseg_id)
132 				!= FIL_NULL;
133 		}
134 	}
135 
136 	mtr.commit();
137 }
138 
139 /*****************************************************************//**
140 Creates the file page for the transaction system. This function is called only
141 at the database creation, before trx_sys_init. */
142 static
143 void
trx_sysf_create(mtr_t * mtr)144 trx_sysf_create(
145 /*============*/
146 	mtr_t*	mtr)	/*!< in: mtr */
147 {
148 	ulint		slot_no;
149 	buf_block_t*	block;
150 
151 	ut_ad(mtr);
152 
153 	/* Note that below we first reserve the file space x-latch, and
154 	then enter the kernel: we must do it in this order to conform
155 	to the latching order rules. */
156 
157 	mtr_x_lock_space(fil_system.sys_space, mtr);
158 	compile_time_assert(TRX_SYS_SPACE == 0);
159 
160 	/* Create the trx sys file block in a new allocated file segment */
161 	block = fseg_create(fil_system.sys_space,
162 			    TRX_SYS + TRX_SYS_FSEG_HEADER,
163 			    mtr);
164 	buf_block_dbg_add_level(block, SYNC_TRX_SYS_HEADER);
165 
166 	ut_a(block->page.id() == page_id_t(0, TRX_SYS_PAGE_NO));
167 
168 	mtr->write<2>(*block, FIL_PAGE_TYPE + block->frame,
169 		      FIL_PAGE_TYPE_TRX_SYS);
170 
171 	ut_ad(!mach_read_from_4(block->frame
172 				+ TRX_SYS_DOUBLEWRITE
173 				+ TRX_SYS_DOUBLEWRITE_MAGIC));
174 
175 	/* Reset the rollback segment slots.  Old versions of InnoDB
176 	(before MySQL 5.5) define TRX_SYS_N_RSEGS as 256 and expect
177 	that the whole array is initialized. */
178 	compile_time_assert(256 >= TRX_SYS_N_RSEGS);
179 	compile_time_assert(TRX_SYS + TRX_SYS_RSEGS
180 			    + 256 * TRX_SYS_RSEG_SLOT_SIZE
181 			    <= UNIV_PAGE_SIZE_MIN - FIL_PAGE_DATA_END);
182 	mtr->memset(block, TRX_SYS + TRX_SYS_RSEGS,
183 		    256 * TRX_SYS_RSEG_SLOT_SIZE, 0xff);
184 	/* Initialize all of the page.  This part used to be uninitialized. */
185 	mtr->memset(block, TRX_SYS + TRX_SYS_RSEGS
186 		    + 256 * TRX_SYS_RSEG_SLOT_SIZE,
187 		    srv_page_size
188 		    - (FIL_PAGE_DATA_END + TRX_SYS + TRX_SYS_RSEGS
189 		       + 256 * TRX_SYS_RSEG_SLOT_SIZE),
190 		    0);
191 
192 	/* Create the first rollback segment in the SYSTEM tablespace */
193 	slot_no = trx_sys_rseg_find_free(block);
194 	buf_block_t* rblock = trx_rseg_header_create(fil_system.sys_space,
195 						     slot_no, 0, block, mtr);
196 
197 	ut_a(slot_no == TRX_SYS_SYSTEM_RSEG_ID);
198 	ut_a(rblock->page.id() == page_id_t(0, FSP_FIRST_RSEG_PAGE_NO));
199 }
200 
201 /** Create the instance */
202 void
create()203 trx_sys_t::create()
204 {
205 	ut_ad(this == &trx_sys);
206 	ut_ad(!is_initialised());
207 	m_initialised = true;
208 	trx_list.create();
209 	rseg_history_len= 0;
210 
211 	rw_trx_hash.init();
212 }
213 
214 /*****************************************************************//**
215 Creates and initializes the transaction system at the database creation. */
216 void
trx_sys_create_sys_pages(void)217 trx_sys_create_sys_pages(void)
218 /*==========================*/
219 {
220 	mtr_t	mtr;
221 
222 	mtr_start(&mtr);
223 
224 	trx_sysf_create(&mtr);
225 
226 	mtr_commit(&mtr);
227 }
228 
229 /** Create the rollback segments.
230 @return	whether the creation succeeded */
231 bool
trx_sys_create_rsegs()232 trx_sys_create_rsegs()
233 {
234 	/* srv_available_undo_logs reflects the number of persistent
235 	rollback segments that have been initialized in the
236 	transaction system header page. */
237 	ut_ad(srv_undo_tablespaces <= TRX_SYS_MAX_UNDO_SPACES);
238 
239 	if (high_level_read_only) {
240 		srv_available_undo_logs = 0;
241 		return(true);
242 	}
243 
244 	/* This is executed in single-threaded mode therefore it is not
245 	necessary to use the same mtr in trx_rseg_create(). n_used cannot
246 	change while the function is executing. */
247 	trx_sysf_get_n_rseg_slots();
248 
249 	ut_ad(srv_available_undo_logs <= TRX_SYS_N_RSEGS);
250 
251 	/* The first persistent rollback segment is always initialized
252 	in the system tablespace. */
253 	ut_a(srv_available_undo_logs > 0);
254 
255 	for (ulint i = 0; srv_available_undo_logs < TRX_SYS_N_RSEGS;
256 	     i++, srv_available_undo_logs++) {
257 		/* Tablespace 0 is the system tablespace.
258 		Dedicated undo log tablespaces start from 1. */
259 		ulint space = srv_undo_tablespaces > 0
260 			? (i % srv_undo_tablespaces)
261 			+ srv_undo_space_id_start
262 			: TRX_SYS_SPACE;
263 
264 		if (!trx_rseg_create(space)) {
265 			ib::error() << "Unable to allocate the"
266 				" requested innodb_undo_logs";
267 			return(false);
268 		}
269 
270 		/* Increase the number of active undo
271 		tablespace in case new rollback segment
272 		assigned to new undo tablespace. */
273 		if (space > srv_undo_tablespaces_active) {
274 			srv_undo_tablespaces_active++;
275 
276 			ut_ad(srv_undo_tablespaces_active == space);
277 		}
278 	}
279 
280 	ut_ad(srv_available_undo_logs == TRX_SYS_N_RSEGS);
281 
282 	ib::info info;
283 	info << srv_available_undo_logs;
284 	if (srv_undo_tablespaces_active) {
285 		info << " rollback segments in " << srv_undo_tablespaces_active
286 		<< " undo tablespaces are active.";
287 	} else {
288 		info << " rollback segments are active.";
289 	}
290 
291 	return(true);
292 }
293 
294 /** Close the transaction system on shutdown */
295 void
close()296 trx_sys_t::close()
297 {
298 	ut_ad(srv_shutdown_state == SRV_SHUTDOWN_EXIT_THREADS);
299 	if (!is_initialised()) {
300 		return;
301 	}
302 
303 	if (size_t size = view_count()) {
304 		ib::error() << "All read views were not closed before"
305 			" shutdown: " << size << " read views open";
306 	}
307 
308 	rw_trx_hash.destroy();
309 
310 	/* There can't be any active transactions. */
311 
312 	for (ulint i = 0; i < TRX_SYS_N_RSEGS; ++i) {
313 		if (trx_rseg_t* rseg = rseg_array[i]) {
314 			trx_rseg_mem_free(rseg);
315 		}
316 
317 		if (trx_rseg_t* rseg = temp_rsegs[i]) {
318 			trx_rseg_mem_free(rseg);
319 		}
320 	}
321 
322 	ut_a(trx_list.empty());
323 	trx_list.close();
324 	m_initialised = false;
325 }
326 
327 /** @return total number of active (non-prepared) transactions */
any_active_transactions()328 ulint trx_sys_t::any_active_transactions()
329 {
330   uint32_t total_trx= 0;
331 
332   trx_sys.trx_list.for_each([&total_trx](const trx_t &trx) {
333     if (trx.state == TRX_STATE_COMMITTED_IN_MEMORY ||
334         (trx.state == TRX_STATE_ACTIVE && trx.id))
335       total_trx++;
336   });
337 
338   return total_trx;
339 }
340