1 /*****************************************************************************
2 
3 Copyright (c) 1996, 2021, Oracle and/or its affiliates.
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License, version 2.0,
7 as published by the Free Software Foundation.
8 
9 This program is also distributed with certain software (including
10 but not limited to OpenSSL) that is licensed under separate terms,
11 as designated in a particular file or component or in included license
12 documentation.  The authors of MySQL hereby grant you an additional
13 permission to link the program and your derivative works with the
14 separately licensed software that they have included with MySQL.
15 
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 GNU General Public License, version 2.0, for more details.
20 
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc.,
23 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
24 
25 *****************************************************************************/
26 
27 /**************************************************//**
28 @file trx/trx0rseg.cc
29 Rollback segment
30 
31 Created 3/26/1996 Heikki Tuuri
32 *******************************************************/
33 
34 #include "trx0rseg.h"
35 
36 #ifdef UNIV_NONINL
37 #include "trx0rseg.ic"
38 #endif
39 
40 #include "trx0undo.h"
41 #include "fut0lst.h"
42 #include "srv0srv.h"
43 #include "trx0purge.h"
44 #include "srv0mon.h"
45 #include "fsp0sysspace.h"
46 
47 #include <algorithm>
48 
49 /** Creates a rollback segment header.
50 This function is called only when a new rollback segment is created in
51 the database.
52 @param[in]	space		space id
53 @param[in]	page_size	page size
54 @param[in]	max_size	max size in pages
55 @param[in]	rseg_slot_no	rseg id == slot number in trx sys
56 @param[in,out]	mtr		mini-transaction
57 @return page number of the created segment, FIL_NULL if fail */
58 ulint
trx_rseg_header_create(ulint space,const page_size_t & page_size,ulint max_size,ulint rseg_slot_no,mtr_t * mtr)59 trx_rseg_header_create(
60 	ulint			space,
61 	const page_size_t&	page_size,
62 	ulint			max_size,
63 	ulint			rseg_slot_no,
64 	mtr_t*			mtr)
65 {
66 	ulint		page_no;
67 	trx_rsegf_t*	rsegf;
68 	trx_sysf_t*	sys_header;
69 	ulint		i;
70 	buf_block_t*	block;
71 
72 	ut_ad(mtr);
73 	ut_ad(mtr_memo_contains(mtr, fil_space_get_latch(space, NULL),
74 				MTR_MEMO_X_LOCK));
75 
76 	/* Allocate a new file segment for the rollback segment */
77 	block = fseg_create(space, 0, TRX_RSEG + TRX_RSEG_FSEG_HEADER, mtr);
78 
79 	if (block == NULL) {
80 		/* No space left */
81 
82 		return(FIL_NULL);
83 	}
84 
85 	buf_block_dbg_add_level(block, SYNC_RSEG_HEADER_NEW);
86 
87 	page_no = block->page.id.page_no();
88 
89 	/* Get the rollback segment file page */
90 	rsegf = trx_rsegf_get_new(space, page_no, page_size, mtr);
91 
92 	/* Initialize max size field */
93 	mlog_write_ulint(rsegf + TRX_RSEG_MAX_SIZE, max_size,
94 			 MLOG_4BYTES, mtr);
95 
96 	/* Initialize the history list */
97 
98 	mlog_write_ulint(rsegf + TRX_RSEG_HISTORY_SIZE, 0, MLOG_4BYTES, mtr);
99 	flst_init(rsegf + TRX_RSEG_HISTORY, mtr);
100 
101 	/* Reset the undo log slots */
102 	for (i = 0; i < TRX_RSEG_N_SLOTS; i++) {
103 
104 		trx_rsegf_set_nth_undo(rsegf, i, FIL_NULL, mtr);
105 	}
106 
107 	if (!trx_sys_is_noredo_rseg_slot(rseg_slot_no)) {
108 		/* Non-redo rseg are re-created on restart and so no need
109 		to persist this information in sys-header. Anyway, on restart
110 		this information is not valid too as there is no space with
111 		persisted space-id on restart. */
112 
113 		/* Add the rollback segment info to the free slot in
114 		the trx system header */
115 
116 		sys_header = trx_sysf_get(mtr);
117 
118 		trx_sysf_rseg_set_space(sys_header, rseg_slot_no, space, mtr);
119 
120 		trx_sysf_rseg_set_page_no(
121 			sys_header, rseg_slot_no, page_no, mtr);
122 	}
123 
124 	return(page_no);
125 }
126 
127 /***********************************************************************//**
128 Free's an instance of the rollback segment in memory. */
129 void
trx_rseg_mem_free(trx_rseg_t * rseg,trx_rseg_t ** rseg_array)130 trx_rseg_mem_free(
131 /*==============*/
132 	trx_rseg_t*	rseg,		/* in, own: instance to free */
133 	trx_rseg_t**	rseg_array)	/*!< out: add rseg reference to this
134 					central array. */
135 {
136 	trx_undo_t*	undo;
137 	trx_undo_t*	next_undo;
138 
139 	mutex_free(&rseg->mutex);
140 
141 	/* There can't be any active transactions. */
142 	ut_a(UT_LIST_GET_LEN(rseg->update_undo_list) == 0);
143 	ut_a(UT_LIST_GET_LEN(rseg->insert_undo_list) == 0);
144 
145 	for (undo = UT_LIST_GET_FIRST(rseg->update_undo_cached);
146 	     undo != NULL;
147 	     undo = next_undo) {
148 
149 		next_undo = UT_LIST_GET_NEXT(undo_list, undo);
150 
151 		UT_LIST_REMOVE(rseg->update_undo_cached, undo);
152 
153 		MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
154 
155 		trx_undo_mem_free(undo);
156 	}
157 
158 	for (undo = UT_LIST_GET_FIRST(rseg->insert_undo_cached);
159 	     undo != NULL;
160 	     undo = next_undo) {
161 
162 		next_undo = UT_LIST_GET_NEXT(undo_list, undo);
163 
164 		UT_LIST_REMOVE(rseg->insert_undo_cached, undo);
165 
166 		MONITOR_DEC(MONITOR_NUM_UNDO_SLOT_CACHED);
167 
168 		trx_undo_mem_free(undo);
169 	}
170 
171 	ut_a(*((trx_rseg_t**) rseg_array + rseg->id) == rseg);
172 	*((trx_rseg_t**) rseg_array + rseg->id) = NULL;
173 
174 	ut_free(rseg);
175 }
176 
177 /** Creates and initializes a rollback segment object.
178 The values for the fields are read from the header. The object is inserted to
179 the rseg list of the trx system object and a pointer is inserted in the rseg
180 array in the trx system object.
181 @param[in]	id		rollback segment id
182 @param[in]	space		space where the segment is placed
183 @param[in]	page_no		page number of the segment header
184 @param[in]	page_size	page size
185 @param[in,out]	purge_queue	rseg queue
186 @param[out]	rseg_array	add rseg reference to this central array
187 @param[in,out]	mtr		mini-transaction
188 @return own: rollback segment object */
189 static
190 trx_rseg_t*
trx_rseg_mem_create(ulint id,ulint space,ulint page_no,const page_size_t & page_size,purge_pq_t * purge_queue,trx_rseg_t ** rseg_array,mtr_t * mtr)191 trx_rseg_mem_create(
192 	ulint			id,
193 	ulint			space,
194 	ulint			page_no,
195 	const page_size_t&	page_size,
196 	purge_pq_t*		purge_queue,
197 	trx_rseg_t**		rseg_array,
198 	mtr_t*			mtr)
199 {
200 	ulint		len;
201 	trx_rseg_t*	rseg;
202 	fil_addr_t	node_addr;
203 	trx_rsegf_t*	rseg_header;
204 	trx_ulogf_t*	undo_log_hdr;
205 	ulint		sum_of_undo_sizes;
206 
207 	rseg = static_cast<trx_rseg_t*>(ut_zalloc_nokey(sizeof(trx_rseg_t)));
208 
209 	rseg->id = id;
210 	rseg->space = space;
211 	rseg->page_size.copy_from(page_size);
212 	rseg->page_no = page_no;
213 	rseg->trx_ref_count = 0;
214 	rseg->skip_allocation = false;
215 
216 	if (fsp_is_system_temporary(space)) {
217 		mutex_create(LATCH_ID_NOREDO_RSEG, &rseg->mutex);
218 	} else {
219 		mutex_create(LATCH_ID_REDO_RSEG, &rseg->mutex);
220 	}
221 
222 	UT_LIST_INIT(rseg->update_undo_list, &trx_undo_t::undo_list);
223 	UT_LIST_INIT(rseg->update_undo_cached, &trx_undo_t::undo_list);
224 	UT_LIST_INIT(rseg->insert_undo_list, &trx_undo_t::undo_list);
225 	UT_LIST_INIT(rseg->insert_undo_cached, &trx_undo_t::undo_list);
226 
227 	*((trx_rseg_t**) rseg_array + rseg->id) = rseg;
228 
229 	rseg_header = trx_rsegf_get_new(space, page_no, page_size, mtr);
230 
231 	rseg->max_size = mtr_read_ulint(
232 		rseg_header + TRX_RSEG_MAX_SIZE, MLOG_4BYTES, mtr);
233 
234 	/* Initialize the undo log lists according to the rseg header */
235 
236 	sum_of_undo_sizes = trx_undo_lists_init(rseg);
237 
238 	rseg->curr_size = mtr_read_ulint(
239 		rseg_header + TRX_RSEG_HISTORY_SIZE, MLOG_4BYTES, mtr)
240 		+ 1 + sum_of_undo_sizes;
241 
242 	len = flst_get_len(rseg_header + TRX_RSEG_HISTORY);
243 
244 	if (len > 0) {
245 		trx_sys->rseg_history_len += len;
246 
247 		node_addr = trx_purge_get_log_from_hist(
248 			flst_get_last(rseg_header + TRX_RSEG_HISTORY, mtr));
249 
250 		rseg->last_page_no = node_addr.page;
251 		rseg->last_offset = node_addr.boffset;
252 
253 		undo_log_hdr = trx_undo_page_get(
254 			page_id_t(rseg->space, node_addr.page),
255 			rseg->page_size, mtr) + node_addr.boffset;
256 
257 		rseg->last_trx_no = mach_read_from_8(
258 			undo_log_hdr + TRX_UNDO_TRX_NO);
259 
260 		rseg->last_del_marks = mtr_read_ulint(
261 			undo_log_hdr + TRX_UNDO_DEL_MARKS, MLOG_2BYTES, mtr);
262 
263 		TrxUndoRsegs elem(rseg->last_trx_no);
264 		elem.push_back(rseg);
265 
266 		if (rseg->last_page_no != FIL_NULL) {
267 
268 			/* There is no need to cover this operation by the purge
269 			mutex because we are still bootstrapping. */
270 
271 			purge_queue->push(elem);
272 		}
273 	} else {
274 		rseg->last_page_no = FIL_NULL;
275 	}
276 
277 	return(rseg);
278 }
279 
280 /********************************************************************
281 Check if rseg in given slot needs to be scheduled for purge. */
282 static
283 void
trx_rseg_schedule_pending_purge(trx_sysf_t * sys_header,purge_pq_t * purge_queue,ulint slot,mtr_t * mtr)284 trx_rseg_schedule_pending_purge(
285 /*============================*/
286 	trx_sysf_t*	sys_header,	/*!< in: trx system header */
287 	purge_pq_t*	purge_queue,	/*!< in/out: rseg queue */
288 	ulint		slot,		/*!< in: check rseg from given slot. */
289 	mtr_t*		mtr)		/*!< in: mtr */
290 {
291 	ulint	page_no;
292 	ulint	space;
293 
294 	page_no = trx_sysf_rseg_get_page_no(sys_header, slot, mtr);
295 	space = trx_sysf_rseg_get_space(sys_header, slot, mtr);
296 
297 	if (page_no != FIL_NULL
298 	    && is_system_or_undo_tablespace(space)) {
299 
300 		/* rseg resides in system or undo tablespace and so
301 		this is an upgrade scenario. trx_rseg_mem_create
302 		will add rseg to purge queue if needed. */
303 
304 		trx_rseg_t*		rseg = NULL;
305 		bool			found = true;
306 		const page_size_t&	page_size
307 			= is_system_tablespace(space)
308 			? univ_page_size
309 			: fil_space_get_page_size(space, &found);
310 
311 		ut_ad(found);
312 
313 		rseg = trx_rseg_mem_create(
314 			slot, space, page_no, page_size,
315 			purge_queue, trx_sys->pending_purge_rseg_array, mtr);
316 
317 		ut_a(rseg->id == slot);
318 	}
319 }
320 
321 /********************************************************************
322 Creates the memory copies for the rollback segments and initializes the
323 rseg array in trx_sys at a database startup. */
324 static
325 void
trx_rseg_create_instance(purge_pq_t * purge_queue)326 trx_rseg_create_instance(
327 /*=====================*/
328 	purge_pq_t*	purge_queue)	/*!< in/out: rseg queue */
329 {
330 	ulint		i;
331 
332 	for (i = 0; i < TRX_SYS_N_RSEGS; i++) {
333 		ulint	page_no;
334 
335 		mtr_t	mtr;
336 		mtr.start();
337 		trx_sysf_t* sys_header = trx_sysf_get(&mtr);
338 
339 		page_no = trx_sysf_rseg_get_page_no(sys_header, i, &mtr);
340 
341 		/* Slot-1....Slot-n are reserved for non-redo rsegs.
342 		Non-redo rsegs are recreated on server re-start so
343 		avoid initializing the existing non-redo rsegs. */
344 		if (trx_sys_is_noredo_rseg_slot(i)) {
345 
346 			/* If this is an upgrade scenario then existing rsegs
347 			in range from slot-1....slot-n needs to be scheduled
348 			for purge if there are pending purge operation. */
349 			trx_rseg_schedule_pending_purge(
350 				sys_header, purge_queue, i, &mtr);
351 
352 		} else if (page_no != FIL_NULL) {
353 			ulint		space;
354 			trx_rseg_t*	rseg = NULL;
355 
356 			ut_a(!trx_rseg_get_on_id(i, true));
357 
358 			space = trx_sysf_rseg_get_space(sys_header, i, &mtr);
359 
360 			bool			found = true;
361 			const page_size_t&	page_size
362 				= is_system_tablespace(space)
363 				? univ_page_size
364 				: fil_space_get_page_size(space, &found);
365 
366 			ut_ad(found);
367 
368 			trx_rseg_t** rseg_array =
369 				static_cast<trx_rseg_t**>(trx_sys->rseg_array);
370 
371 			rseg = trx_rseg_mem_create(
372 				i, space, page_no, page_size,
373 				purge_queue, rseg_array, &mtr);
374 
375 			ut_a(rseg->id == i);
376 		} else {
377 			ut_a(trx_sys->rseg_array[i] == NULL);
378 		}
379 		mtr.commit();
380 	}
381 }
382 
383 /*********************************************************************
384 Creates a rollback segment.
385 @return pointer to new rollback segment if create successful */
386 trx_rseg_t*
trx_rseg_create(ulint space_id,ulint nth_free_slot)387 trx_rseg_create(
388 /*============*/
389 	ulint	space_id,	/*!< in: id of UNDO tablespace */
390 	ulint	nth_free_slot)	/*!< in: allocate nth free slot.
391 				0 means next free slots. */
392 {
393 	mtr_t		mtr;
394 	ulint		slot_no;
395 	trx_rseg_t*	rseg = NULL;
396 
397 	mtr_start(&mtr);
398 
399 	/* To obey the latching order, acquire the file space
400 	x-latch before the trx_sys->mutex. */
401 	const fil_space_t*	space = mtr_x_lock_space(space_id, &mtr);
402 
403 	switch (space->purpose) {
404 	case FIL_TYPE_LOG:
405 	case FIL_TYPE_IMPORT:
406 		ut_ad(0);
407 	case FIL_TYPE_TEMPORARY:
408 		mtr_set_log_mode(&mtr, MTR_LOG_NO_REDO);
409 		break;
410 	case FIL_TYPE_TABLESPACE:
411 		break;
412 	}
413 
414 	slot_no = trx_sysf_rseg_find_free(
415 		&mtr, space->purpose == FIL_TYPE_TEMPORARY, nth_free_slot);
416 
417 	if (slot_no != ULINT_UNDEFINED) {
418 		ulint		id;
419 		ulint		page_no;
420 		trx_sysf_t*	sys_header;
421 		page_size_t	page_size(space->flags);
422 
423 		page_no = trx_rseg_header_create(
424 			space_id, page_size, ULINT_MAX, slot_no, &mtr);
425 
426 		if (page_no == FIL_NULL) {
427 			mtr_commit(&mtr);
428 
429 			return(rseg);
430 		}
431 
432 		sys_header = trx_sysf_get(&mtr);
433 
434 		id = trx_sysf_rseg_get_space(sys_header, slot_no, &mtr);
435 		ut_a(id == space_id || trx_sys_is_noredo_rseg_slot(slot_no));
436 
437 		trx_rseg_t** rseg_array =
438 			((trx_rseg_t**) trx_sys->rseg_array);
439 
440 		rseg = trx_rseg_mem_create(
441 			slot_no, space_id, page_no, page_size,
442 			purge_sys->purge_queue, rseg_array, &mtr);
443 	}
444 
445 	mtr_commit(&mtr);
446 
447 	return(rseg);
448 }
449 
450 /*********************************************************************//**
451 Creates the memory copies for rollback segments and initializes the
452 rseg array in trx_sys at a database startup. */
453 void
trx_rseg_array_init(purge_pq_t * purge_queue)454 trx_rseg_array_init(
455 /*================*/
456 	purge_pq_t*	purge_queue)	/*!< in: rseg queue */
457 {
458 	trx_sys->rseg_history_len = 0;
459 
460 	trx_rseg_create_instance(purge_queue);
461 }
462 
463 /********************************************************************
464 Get the number of unique rollback tablespaces in use except space id 0.
465 The last space id will be the sentinel value ULINT_UNDEFINED. The array
466 will be sorted on space id. Note: space_ids should have have space for
467 TRX_SYS_N_RSEGS + 1 elements.
468 @return number of unique rollback tablespaces in use. */
469 ulint
trx_rseg_get_n_undo_tablespaces(ulint * space_ids)470 trx_rseg_get_n_undo_tablespaces(
471 /*============================*/
472 	ulint*		space_ids)	/*!< out: array of space ids of
473 					UNDO tablespaces */
474 {
475 	ulint		i;
476 	mtr_t		mtr;
477 	trx_sysf_t*	sys_header;
478 	ulint		n_undo_tablespaces = 0;
479 
480 	mtr_start(&mtr);
481 
482 	sys_header = trx_sysf_get(&mtr);
483 
484 	for (i = 0; i < TRX_SYS_N_RSEGS; i++) {
485 		ulint	page_no;
486 		ulint	space;
487 
488 		page_no = trx_sysf_rseg_get_page_no(sys_header, i, &mtr);
489 
490 		if (page_no == FIL_NULL) {
491 			continue;
492 		}
493 
494 		space = trx_sysf_rseg_get_space(sys_header, i, &mtr);
495 
496 		if (space != 0) {
497 			ulint	j;
498 			ibool	found = FALSE;
499 
500 			for (j = 0; j < n_undo_tablespaces; ++j) {
501 				if (space_ids[j] == space) {
502 					found = TRUE;
503 					break;
504 				}
505 			}
506 
507 			if (!found) {
508 				ut_a(n_undo_tablespaces <= i);
509 				space_ids[n_undo_tablespaces++] = space;
510 			}
511 		}
512 	}
513 
514 	mtr_commit(&mtr);
515 
516 	ut_a(n_undo_tablespaces <= TRX_SYS_N_RSEGS);
517 
518 	space_ids[n_undo_tablespaces] = ULINT_UNDEFINED;
519 
520 	if (n_undo_tablespaces > 0) {
521 		std::sort(space_ids, space_ids + n_undo_tablespaces);
522 	}
523 
524 	return(n_undo_tablespaces);
525 }
526