1
/******************************************************
6
Created 3/26/1996 Heikki Tuuri
7
*******************************************************/
12
#include "trx0roll.ic"
16
#include "mach0data.h"
24
#include "srv0start.h"
26
#include "row0mysql.h"
27
#include "lock0lock.h"
28
#include "pars0pars.h"
30
/* This many pages must be undone before a truncate is tried within rollback */
31
#define TRX_ROLL_TRUNC_THRESHOLD 1
33
/* In crash recovery, the current trx to be rolled back */
34
static trx_t* trx_roll_crash_recv_trx = NULL;
36
/* In crash recovery we set this to the undo n:o of the current trx to be
37
rolled back. Then we can print how many % the rollback has progressed. */
38
static ib_int64_t trx_roll_max_undo_no;
40
/* Auxiliary variable which tells the previous progress % we printed */
41
static ulint trx_roll_progress_printed_pct;
43
/***********************************************************************
44
Rollback a transaction used in MySQL. */
47
trx_general_rollback_for_mysql(
48
/*===========================*/
49
/* out: error code or DB_SUCCESS */
50
trx_t* trx, /* in: transaction handle */
51
ibool partial,/* in: TRUE if partial rollback requested */
52
trx_savept_t* savept) /* in: pointer to savepoint undo number, if
53
partial rollback requested */
55
#ifndef UNIV_HOTBACKUP
58
roll_node_t* roll_node;
60
/* Tell Innobase server that there might be work for
63
srv_active_wake_master_thread();
65
trx_start_if_not_started(trx);
67
heap = mem_heap_create(512);
69
roll_node = roll_node_create(heap);
71
roll_node->partial = partial;
74
roll_node->savept = *savept;
77
trx->error_state = DB_SUCCESS;
79
thr = pars_complete_graph_for_exec(roll_node, trx, heap);
81
ut_a(thr == que_fork_start_command(que_node_get_parent(thr)));
84
mutex_enter(&kernel_mutex);
86
while (trx->que_state != TRX_QUE_RUNNING) {
88
mutex_exit(&kernel_mutex);
90
os_thread_sleep(100000);
92
mutex_enter(&kernel_mutex);
95
mutex_exit(&kernel_mutex);
99
ut_a(trx->error_state == DB_SUCCESS);
101
/* Tell Innobase server that there might be work for
104
srv_active_wake_master_thread();
106
return((int) trx->error_state);
107
#else /* UNIV_HOTBACKUP */
108
/* This function depends on MySQL code that is not included in
109
InnoDB Hot Backup builds. Besides, this function should never
110
be called in InnoDB Hot Backup. */
113
#endif /* UNIV_HOTBACKUP */
116
/***********************************************************************
117
Rollback a transaction used in MySQL. */
120
trx_rollback_for_mysql(
121
/*===================*/
122
/* out: error code or DB_SUCCESS */
123
trx_t* trx) /* in: transaction handle */
127
if (trx->conc_state == TRX_NOT_STARTED) {
132
trx->op_info = "rollback";
134
/* If we are doing the XA recovery of prepared transactions, then
135
the transaction object does not have an InnoDB session object, and we
136
set a dummy session that we use for all MySQL transactions. */
138
err = trx_general_rollback_for_mysql(trx, FALSE, NULL);
145
/***********************************************************************
146
Rollback the latest SQL statement for MySQL. */
149
trx_rollback_last_sql_stat_for_mysql(
150
/*=================================*/
151
/* out: error code or DB_SUCCESS */
152
trx_t* trx) /* in: transaction handle */
156
if (trx->conc_state == TRX_NOT_STARTED) {
161
trx->op_info = "rollback of SQL statement";
163
err = trx_general_rollback_for_mysql(trx, TRUE,
164
&(trx->last_sql_stat_start));
165
/* The following call should not be needed, but we play safe: */
166
trx_mark_sql_stat_end(trx);
173
/***********************************************************************
174
Frees savepoint structs. */
177
trx_roll_savepoints_free(
178
/*=====================*/
179
trx_t* trx, /* in: transaction handle */
180
trx_named_savept_t* savep) /* in: free all savepoints > this one;
181
if this is NULL, free all savepoints
184
trx_named_savept_t* next_savep;
187
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
189
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
192
while (savep != NULL) {
193
next_savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
195
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
196
mem_free(savep->name);
203
/***********************************************************************
204
Rolls back a transaction back to a named savepoint. Modifications after the
205
savepoint are undone but InnoDB does NOT release the corresponding locks
206
which are stored in memory. If a lock is 'implicit', that is, a new inserted
207
row holds a lock where the lock information is carried by the trx id stored in
208
the row, these locks are naturally released in the rollback. Savepoints which
209
were set after this savepoint are deleted. */
212
trx_rollback_to_savepoint_for_mysql(
213
/*================================*/
214
/* out: if no savepoint
215
of the name found then
217
otherwise DB_SUCCESS */
218
trx_t* trx, /* in: transaction handle */
219
const char* savepoint_name, /* in: savepoint name */
220
ib_int64_t* mysql_binlog_cache_pos) /* out: the MySQL binlog cache
221
position corresponding to this
222
savepoint; MySQL needs this
223
information to remove the
224
binlog entries of the queries
225
executed after the savepoint */
227
trx_named_savept_t* savep;
230
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
232
while (savep != NULL) {
233
if (0 == ut_strcmp(savep->name, savepoint_name)) {
237
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
242
return(DB_NO_SAVEPOINT);
245
if (trx->conc_state == TRX_NOT_STARTED) {
246
ut_print_timestamp(stderr);
247
fputs(" InnoDB: Error: transaction has a savepoint ", stderr);
248
ut_print_name(stderr, trx, FALSE, savep->name);
249
fputs(" though it is not started\n", stderr);
253
/* We can now free all savepoints strictly later than this one */
255
trx_roll_savepoints_free(trx, savep);
257
*mysql_binlog_cache_pos = savep->mysql_binlog_cache_pos;
259
trx->op_info = "rollback to a savepoint";
261
err = trx_general_rollback_for_mysql(trx, TRUE, &(savep->savept));
263
/* Store the current undo_no of the transaction so that we know where
264
to roll back if we have to roll back the next SQL statement: */
266
trx_mark_sql_stat_end(trx);
273
/***********************************************************************
274
Creates a named savepoint. If the transaction is not yet started, starts it.
275
If there is already a savepoint of the same name, this call erases that old
276
savepoint and replaces it with a new. Savepoints are deleted in a transaction
277
commit or rollback. */
280
trx_savepoint_for_mysql(
281
/*====================*/
282
/* out: always DB_SUCCESS */
283
trx_t* trx, /* in: transaction handle */
284
const char* savepoint_name, /* in: savepoint name */
285
ib_int64_t binlog_cache_pos) /* in: MySQL binlog cache
286
position corresponding to this
287
connection at the time of the
290
trx_named_savept_t* savep;
293
ut_a(savepoint_name);
295
trx_start_if_not_started(trx);
297
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
299
while (savep != NULL) {
300
if (0 == ut_strcmp(savep->name, savepoint_name)) {
304
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
308
/* There is a savepoint with the same name: free that */
310
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
312
mem_free(savep->name);
316
/* Create a new savepoint and add it as the last in the list */
318
savep = mem_alloc(sizeof(trx_named_savept_t));
320
savep->name = mem_strdup(savepoint_name);
322
savep->savept = trx_savept_take(trx);
324
savep->mysql_binlog_cache_pos = binlog_cache_pos;
326
UT_LIST_ADD_LAST(trx_savepoints, trx->trx_savepoints, savep);
331
/***********************************************************************
332
Releases a named savepoint. Savepoints which
333
were set after this savepoint are deleted. */
336
trx_release_savepoint_for_mysql(
337
/*============================*/
338
/* out: if no savepoint
339
of the name found then
341
otherwise DB_SUCCESS */
342
trx_t* trx, /* in: transaction handle */
343
const char* savepoint_name) /* in: savepoint name */
345
trx_named_savept_t* savep;
347
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
349
while (savep != NULL) {
350
if (0 == ut_strcmp(savep->name, savepoint_name)) {
354
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
359
return(DB_NO_SAVEPOINT);
362
/* We can now free all savepoints strictly later than this one */
364
trx_roll_savepoints_free(trx, savep);
366
/* Now we can free this savepoint too */
368
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
370
mem_free(savep->name);
376
/***********************************************************************
377
Returns a transaction savepoint taken at this point in time. */
383
trx_t* trx) /* in: transaction */
387
savept.least_undo_no = trx->undo_no;
392
/***********************************************************************
393
Roll back an active transaction. */
398
trx_t* trx) /* in/out: transaction */
403
roll_node_t* roll_node;
405
ib_int64_t rows_to_undo;
406
const char* unit = "";
407
ibool dictionary_locked = FALSE;
409
heap = mem_heap_create(512);
411
fork = que_fork_create(NULL, NULL, QUE_FORK_RECOVERY, heap);
414
thr = que_thr_create(fork, heap);
416
roll_node = roll_node_create(heap);
418
thr->child = roll_node;
419
roll_node->common.parent = thr;
421
mutex_enter(&kernel_mutex);
425
ut_a(thr == que_fork_start_command(fork));
427
trx_roll_crash_recv_trx = trx;
428
trx_roll_max_undo_no = ut_conv_dulint_to_longlong(trx->undo_no);
429
trx_roll_progress_printed_pct = 0;
430
rows_to_undo = trx_roll_max_undo_no;
432
if (rows_to_undo > 1000000000) {
433
rows_to_undo = rows_to_undo / 1000000;
437
ut_print_timestamp(stderr);
439
" InnoDB: Rolling back trx with id " TRX_ID_FMT ", %lu%s"
441
TRX_ID_PREP_PRINTF(trx->id),
442
(ulong) rows_to_undo, unit);
443
mutex_exit(&kernel_mutex);
445
trx->mysql_thread_id = os_thread_get_curr_id();
447
trx->mysql_process_no = os_proc_get_number();
449
if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE) {
450
row_mysql_lock_data_dictionary(trx);
451
dictionary_locked = TRUE;
454
que_run_threads(thr);
456
mutex_enter(&kernel_mutex);
458
while (trx->que_state != TRX_QUE_RUNNING) {
460
mutex_exit(&kernel_mutex);
463
"InnoDB: Waiting for rollback of trx id %lu to end\n",
464
(ulong) ut_dulint_get_low(trx->id));
465
os_thread_sleep(100000);
467
mutex_enter(&kernel_mutex);
470
mutex_exit(&kernel_mutex);
472
if (trx_get_dict_operation(trx) != TRX_DICT_OP_NONE
473
&& !ut_dulint_is_zero(trx->table_id)) {
475
/* If the transaction was for a dictionary operation, we
476
drop the relevant table, if it still exists */
479
"InnoDB: Dropping table with id %lu %lu"
480
" in recovery if it exists\n",
481
(ulong) ut_dulint_get_high(trx->table_id),
482
(ulong) ut_dulint_get_low(trx->table_id));
484
table = dict_table_get_on_id_low(trx->table_id);
489
fputs("InnoDB: Table found: dropping table ", stderr);
490
ut_print_name(stderr, trx, TRUE, table->name);
491
fputs(" in recovery\n", stderr);
493
err = row_drop_table_for_mysql(table->name, trx, TRUE);
495
ut_a(err == (int) DB_SUCCESS);
499
if (dictionary_locked) {
500
row_mysql_unlock_data_dictionary(trx);
503
fprintf(stderr, "\nInnoDB: Rolling back of trx id " TRX_ID_FMT
505
TRX_ID_PREP_PRINTF(trx->id));
508
trx_roll_crash_recv_trx = NULL;
511
/***********************************************************************
512
Rollback or clean up any incomplete transactions which were
513
encountered in crash recovery. If the transaction already was
514
committed, then we clean up a possible insert undo log. If the
515
transaction was not yet committed, then we roll it back.
516
Note: this is done in a background thread. */
519
trx_rollback_or_clean_all_recovered(
520
/*================================*/
521
/* out: a dummy parameter */
522
void* arg __attribute__((unused)))
523
/* in: a dummy parameter required by
528
mutex_enter(&kernel_mutex);
530
if (UT_LIST_GET_FIRST(trx_sys->trx_list)) {
533
"InnoDB: Starting in background the rollback"
534
" of uncommitted transactions\n");
539
mutex_exit(&kernel_mutex);
542
mutex_enter(&kernel_mutex);
544
for (trx = UT_LIST_GET_FIRST(trx_sys->trx_list); trx;
545
trx = UT_LIST_GET_NEXT(trx_list, trx)) {
546
if (!trx->is_recovered) {
550
switch (trx->conc_state) {
551
case TRX_NOT_STARTED:
555
case TRX_COMMITTED_IN_MEMORY:
556
mutex_exit(&kernel_mutex);
558
"InnoDB: Cleaning up trx with id "
560
TRX_ID_PREP_PRINTF(trx->id));
561
trx_cleanup_at_db_startup(trx);
565
mutex_exit(&kernel_mutex);
566
trx_rollback_active(trx);
571
ut_print_timestamp(stderr);
573
" InnoDB: Rollback of non-prepared transactions completed\n");
576
mutex_exit(&kernel_mutex);
578
/* We count the number of threads in os_thread_exit(). A created
579
thread should always use that to exit and not use return() to exit. */
581
os_thread_exit(NULL);
583
OS_THREAD_DUMMY_RETURN;
586
/***********************************************************************
587
Creates an undo number array. */
590
trx_undo_arr_create(void)
591
/*=====================*/
597
heap = mem_heap_create(1024);
599
arr = mem_heap_alloc(heap, sizeof(trx_undo_arr_t));
601
arr->infos = mem_heap_alloc(heap, sizeof(trx_undo_inf_t)
602
* UNIV_MAX_PARALLELISM);
603
arr->n_cells = UNIV_MAX_PARALLELISM;
608
for (i = 0; i < UNIV_MAX_PARALLELISM; i++) {
610
(trx_undo_arr_get_nth_info(arr, i))->in_use = FALSE;
616
/***********************************************************************
617
Frees an undo number array. */
622
trx_undo_arr_t* arr) /* in: undo number array */
624
ut_ad(arr->n_used == 0);
626
mem_heap_free(arr->heap);
629
/***********************************************************************
630
Stores info of an undo log record to the array if it is not stored yet. */
633
trx_undo_arr_store_info(
634
/*====================*/
635
/* out: FALSE if the record already existed in the
637
trx_t* trx, /* in: transaction */
638
dulint undo_no)/* in: undo number */
640
trx_undo_inf_t* cell;
641
trx_undo_inf_t* stored_here;
648
arr = trx->undo_no_arr;
649
n_used = arr->n_used;
653
cell = trx_undo_arr_get_nth_info(arr, i);
657
/* Not in use, we may store here */
658
cell->undo_no = undo_no;
668
if (0 == ut_dulint_cmp(cell->undo_no, undo_no)) {
671
stored_here->in_use = FALSE;
672
ut_ad(arr->n_used > 0);
676
ut_ad(arr->n_used == n_used);
682
if (n == n_used && stored_here) {
684
ut_ad(arr->n_used == 1 + n_used);
691
/***********************************************************************
692
Removes an undo number from the array. */
695
trx_undo_arr_remove_info(
696
/*=====================*/
697
trx_undo_arr_t* arr, /* in: undo number array */
698
dulint undo_no)/* in: undo number */
700
trx_undo_inf_t* cell;
705
n_used = arr->n_used;
709
cell = trx_undo_arr_get_nth_info(arr, i);
712
&& 0 == ut_dulint_cmp(cell->undo_no, undo_no)) {
714
cell->in_use = FALSE;
716
ut_ad(arr->n_used > 0);
725
/***********************************************************************
726
Gets the biggest undo number in an array. */
729
trx_undo_arr_get_biggest(
730
/*=====================*/
731
/* out: biggest value, ut_dulint_zero if
732
the array is empty */
733
trx_undo_arr_t* arr) /* in: undo number array */
735
trx_undo_inf_t* cell;
742
n_used = arr->n_used;
743
biggest = ut_dulint_zero;
746
cell = trx_undo_arr_get_nth_info(arr, i);
750
if (ut_dulint_cmp(cell->undo_no, biggest) > 0) {
752
biggest = cell->undo_no;
762
/***************************************************************************
763
Tries truncate the undo logs. */
766
trx_roll_try_truncate(
767
/*==================*/
768
trx_t* trx) /* in: transaction */
774
ut_ad(mutex_own(&(trx->undo_mutex)));
775
ut_ad(mutex_own(&((trx->rseg)->mutex)));
777
trx->pages_undone = 0;
779
arr = trx->undo_no_arr;
781
limit = trx->undo_no;
783
if (arr->n_used > 0) {
784
biggest = trx_undo_arr_get_biggest(arr);
786
if (ut_dulint_cmp(biggest, limit) >= 0) {
788
limit = ut_dulint_add(biggest, 1);
792
if (trx->insert_undo) {
793
trx_undo_truncate_end(trx, trx->insert_undo, limit);
796
if (trx->update_undo) {
797
trx_undo_truncate_end(trx, trx->update_undo, limit);
801
/***************************************************************************
802
Pops the topmost undo log record in a single undo log and updates the info
803
about the topmost record in the undo log memory struct. */
806
trx_roll_pop_top_rec(
807
/*=================*/
808
/* out: undo log record, the page s-latched */
809
trx_t* trx, /* in: transaction */
810
trx_undo_t* undo, /* in: undo log */
811
mtr_t* mtr) /* in: mtr */
815
trx_undo_rec_t* prev_rec;
816
page_t* prev_rec_page;
818
ut_ad(mutex_own(&(trx->undo_mutex)));
820
undo_page = trx_undo_page_get_s_latched(undo->space, undo->zip_size,
821
undo->top_page_no, mtr);
822
offset = undo->top_offset;
824
/* fprintf(stderr, "Thread %lu undoing trx %lu undo record %lu\n",
825
os_thread_get_curr_id(), ut_dulint_get_low(trx->id),
826
ut_dulint_get_low(undo->top_undo_no)); */
828
prev_rec = trx_undo_get_prev_rec(undo_page + offset,
829
undo->hdr_page_no, undo->hdr_offset,
831
if (prev_rec == NULL) {
835
prev_rec_page = page_align(prev_rec);
837
if (prev_rec_page != undo_page) {
842
undo->top_page_no = page_get_page_no(prev_rec_page);
843
undo->top_offset = prev_rec - prev_rec_page;
844
undo->top_undo_no = trx_undo_rec_get_undo_no(prev_rec);
847
return(undo_page + offset);
850
/************************************************************************
851
Pops the topmost record when the two undo logs of a transaction are seen
852
as a single stack of records ordered by their undo numbers. Inserts the
853
undo number of the popped undo record to the array of currently processed
854
undo numbers in the transaction. When the query thread finishes processing
855
of this undo record, it must be released with trx_undo_rec_release. */
858
trx_roll_pop_top_rec_of_trx(
859
/*========================*/
860
/* out: undo log record copied to heap, NULL
861
if none left, or if the undo number of the
862
top record would be less than the limit */
863
trx_t* trx, /* in: transaction */
864
dulint limit, /* in: least undo number we need */
865
dulint* roll_ptr,/* out: roll pointer to undo record */
866
mem_heap_t* heap) /* in: memory heap where copied */
869
trx_undo_t* ins_undo;
870
trx_undo_t* upd_undo;
871
trx_undo_rec_t* undo_rec;
872
trx_undo_rec_t* undo_rec_copy;
881
mutex_enter(&(trx->undo_mutex));
883
if (trx->pages_undone >= TRX_ROLL_TRUNC_THRESHOLD) {
884
mutex_enter(&(rseg->mutex));
886
trx_roll_try_truncate(trx);
888
mutex_exit(&(rseg->mutex));
891
ins_undo = trx->insert_undo;
892
upd_undo = trx->update_undo;
894
if (!ins_undo || ins_undo->empty) {
896
} else if (!upd_undo || upd_undo->empty) {
898
} else if (ut_dulint_cmp(upd_undo->top_undo_no,
899
ins_undo->top_undo_no) > 0) {
905
if (!undo || undo->empty
906
|| (ut_dulint_cmp(limit, undo->top_undo_no) > 0)) {
908
if ((trx->undo_no_arr)->n_used == 0) {
909
/* Rollback is ending */
911
mutex_enter(&(rseg->mutex));
913
trx_roll_try_truncate(trx);
915
mutex_exit(&(rseg->mutex));
918
mutex_exit(&(trx->undo_mutex));
923
if (undo == ins_undo) {
929
*roll_ptr = trx_undo_build_roll_ptr(is_insert, (undo->rseg)->id,
934
undo_rec = trx_roll_pop_top_rec(trx, undo, &mtr);
936
undo_no = trx_undo_rec_get_undo_no(undo_rec);
938
ut_ad(ut_dulint_cmp(ut_dulint_add(undo_no, 1), trx->undo_no) == 0);
940
/* We print rollback progress info if we are in a crash recovery
941
and the transaction has at least 1000 row operations to undo. */
943
if (trx == trx_roll_crash_recv_trx && trx_roll_max_undo_no > 1000) {
945
progress_pct = 100 - (ulint)
946
((ut_conv_dulint_to_longlong(undo_no) * 100)
947
/ trx_roll_max_undo_no);
948
if (progress_pct != trx_roll_progress_printed_pct) {
949
if (trx_roll_progress_printed_pct == 0) {
951
"\nInnoDB: Progress in percents:"
952
" %lu", (ulong) progress_pct);
955
" %lu", (ulong) progress_pct);
958
trx_roll_progress_printed_pct = progress_pct;
962
trx->undo_no = undo_no;
964
if (!trx_undo_arr_store_info(trx, undo_no)) {
965
/* A query thread is already processing this undo log record */
967
mutex_exit(&(trx->undo_mutex));
974
undo_rec_copy = trx_undo_rec_copy(undo_rec, heap);
976
mutex_exit(&(trx->undo_mutex));
980
return(undo_rec_copy);
983
/************************************************************************
984
Reserves an undo log record for a query thread to undo. This should be
985
called if the query thread gets the undo log record not using the pop
989
trx_undo_rec_reserve(
990
/*=================*/
991
/* out: TRUE if succeeded */
992
trx_t* trx, /* in: transaction */
993
dulint undo_no)/* in: undo number of the record */
997
mutex_enter(&(trx->undo_mutex));
999
ret = trx_undo_arr_store_info(trx, undo_no);
1001
mutex_exit(&(trx->undo_mutex));
1006
/***********************************************************************
1007
Releases a reserved undo record. */
1010
trx_undo_rec_release(
1011
/*=================*/
1012
trx_t* trx, /* in: transaction */
1013
dulint undo_no)/* in: undo number */
1015
trx_undo_arr_t* arr;
1017
mutex_enter(&(trx->undo_mutex));
1019
arr = trx->undo_no_arr;
1021
trx_undo_arr_remove_info(arr, undo_no);
1023
mutex_exit(&(trx->undo_mutex));
1026
/*************************************************************************
1027
Starts a rollback operation. */
1032
trx_t* trx, /* in: transaction */
1033
trx_sig_t* sig, /* in: signal starting the rollback */
1034
que_thr_t** next_thr)/* in/out: next query thread to run;
1035
if the value which is passed in is
1036
a pointer to a NULL pointer, then the
1037
calling function can start running
1038
a new query thread; if the passed value is
1039
NULL, the parameter is ignored */
1043
/* que_thr_t* thr2; */
1045
ut_ad(mutex_own(&kernel_mutex));
1046
ut_ad((trx->undo_no_arr == NULL) || ((trx->undo_no_arr)->n_used == 0));
1048
/* Initialize the rollback field in the transaction */
1050
if (sig->type == TRX_SIG_TOTAL_ROLLBACK) {
1052
trx->roll_limit = ut_dulint_zero;
1054
} else if (sig->type == TRX_SIG_ROLLBACK_TO_SAVEPT) {
1056
trx->roll_limit = (sig->savept).least_undo_no;
1058
} else if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1060
trx->roll_limit = trx->last_sql_stat_start.least_undo_no;
1065
ut_a(ut_dulint_cmp(trx->roll_limit, trx->undo_no) <= 0);
1067
trx->pages_undone = 0;
1069
if (trx->undo_no_arr == NULL) {
1070
trx->undo_no_arr = trx_undo_arr_create();
1073
/* Build a 'query' graph which will perform the undo operations */
1075
roll_graph = trx_roll_graph_build(trx);
1077
trx->graph = roll_graph;
1078
trx->que_state = TRX_QUE_ROLLING_BACK;
1080
thr = que_fork_start_command(roll_graph);
1084
/* thr2 = que_fork_start_command(roll_graph);
1088
if (next_thr && (*next_thr == NULL)) {
1090
/* srv_que_task_enqueue_low(thr2); */
1092
srv_que_task_enqueue_low(thr);
1093
/* srv_que_task_enqueue_low(thr2); */
1097
/********************************************************************
1098
Builds an undo 'query' graph for a transaction. The actual rollback is
1099
performed by executing this query graph like a query subprocedure call.
1100
The reply about the completion of the rollback will be sent by this
1104
trx_roll_graph_build(
1105
/*=================*/
1106
/* out, own: the query graph */
1107
trx_t* trx) /* in: trx handle */
1112
/* que_thr_t* thr2; */
1114
ut_ad(mutex_own(&kernel_mutex));
1116
heap = mem_heap_create(512);
1117
fork = que_fork_create(NULL, NULL, QUE_FORK_ROLLBACK, heap);
1120
thr = que_thr_create(fork, heap);
1121
/* thr2 = que_thr_create(fork, heap); */
1123
thr->child = row_undo_node_create(trx, thr, heap);
1124
/* thr2->child = row_undo_node_create(trx, thr2, heap); */
1129
/*************************************************************************
1130
Finishes error processing after the necessary partial rollback has been
1134
trx_finish_error_processing(
1135
/*========================*/
1136
trx_t* trx) /* in: transaction */
1139
trx_sig_t* next_sig;
1141
ut_ad(mutex_own(&kernel_mutex));
1143
sig = UT_LIST_GET_FIRST(trx->signals);
1145
while (sig != NULL) {
1146
next_sig = UT_LIST_GET_NEXT(signals, sig);
1148
if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1150
trx_sig_remove(trx, sig);
1156
trx->que_state = TRX_QUE_RUNNING;
1159
/*************************************************************************
1160
Finishes a partial rollback operation. */
1163
trx_finish_partial_rollback_off_kernel(
1164
/*===================================*/
1165
trx_t* trx, /* in: transaction */
1166
que_thr_t** next_thr)/* in/out: next query thread to run;
1167
if the value which is passed in is a pointer
1168
to a NULL pointer, then the calling function
1169
can start running a new query thread; if this
1170
parameter is NULL, it is ignored */
1174
ut_ad(mutex_own(&kernel_mutex));
1176
sig = UT_LIST_GET_FIRST(trx->signals);
1178
/* Remove the signal from the signal queue and send reply message
1181
trx_sig_reply(sig, next_thr);
1182
trx_sig_remove(trx, sig);
1184
trx->que_state = TRX_QUE_RUNNING;
1187
/********************************************************************
1188
Finishes a transaction rollback. */
1191
trx_finish_rollback_off_kernel(
1192
/*===========================*/
1193
que_t* graph, /* in: undo graph which can now be freed */
1194
trx_t* trx, /* in: transaction */
1195
que_thr_t** next_thr)/* in/out: next query thread to run;
1196
if the value which is passed in is
1197
a pointer to a NULL pointer, then the
1198
calling function can start running
1199
a new query thread; if this parameter is
1200
NULL, it is ignored */
1203
trx_sig_t* next_sig;
1205
ut_ad(mutex_own(&kernel_mutex));
1207
ut_a(trx->undo_no_arr == NULL || trx->undo_no_arr->n_used == 0);
1209
/* Free the memory reserved by the undo graph */
1210
que_graph_free(graph);
1212
sig = UT_LIST_GET_FIRST(trx->signals);
1214
if (sig->type == TRX_SIG_ROLLBACK_TO_SAVEPT) {
1216
trx_finish_partial_rollback_off_kernel(trx, next_thr);
1220
} else if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1222
trx_finish_error_processing(trx);
1228
if (lock_print_waits) {
1229
fprintf(stderr, "Trx %lu rollback finished\n",
1230
(ulong) ut_dulint_get_low(trx->id));
1232
#endif /* UNIV_DEBUG */
1234
trx_commit_off_kernel(trx);
1236
/* Remove all TRX_SIG_TOTAL_ROLLBACK signals from the signal queue and
1237
send reply messages to them */
1239
trx->que_state = TRX_QUE_RUNNING;
1241
while (sig != NULL) {
1242
next_sig = UT_LIST_GET_NEXT(signals, sig);
1244
if (sig->type == TRX_SIG_TOTAL_ROLLBACK) {
1246
trx_sig_reply(sig, next_thr);
1248
trx_sig_remove(trx, sig);
1255
/*************************************************************************
1256
Creates a rollback command node struct. */
1261
/* out, own: rollback node struct */
1262
mem_heap_t* heap) /* in: mem heap where created */
1266
node = mem_heap_alloc(heap, sizeof(roll_node_t));
1267
node->common.type = QUE_NODE_ROLLBACK;
1268
node->state = ROLL_NODE_SEND;
1270
node->partial = FALSE;
1275
/***************************************************************
1276
Performs an execution step for a rollback command node in a query graph. */
1281
/* out: query thread to run next, or NULL */
1282
que_thr_t* thr) /* in: query thread */
1286
trx_savept_t* savept;
1288
node = thr->run_node;
1290
ut_ad(que_node_get_type(node) == QUE_NODE_ROLLBACK);
1292
if (thr->prev_node == que_node_get_parent(node)) {
1293
node->state = ROLL_NODE_SEND;
1296
if (node->state == ROLL_NODE_SEND) {
1297
mutex_enter(&kernel_mutex);
1299
node->state = ROLL_NODE_WAIT;
1301
if (node->partial) {
1302
sig_no = TRX_SIG_ROLLBACK_TO_SAVEPT;
1303
savept = &(node->savept);
1305
sig_no = TRX_SIG_TOTAL_ROLLBACK;
1309
/* Send a rollback signal to the transaction */
1311
trx_sig_send(thr_get_trx(thr), sig_no, TRX_SIG_SELF, thr,
1314
thr->state = QUE_THR_SIG_REPLY_WAIT;
1316
mutex_exit(&kernel_mutex);
1321
ut_ad(node->state == ROLL_NODE_WAIT);
1323
thr->run_node = que_node_get_parent(node);