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
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
ib_longlong trx_roll_max_undo_no;
40
/* Auxiliary variable which tells the previous progress % we printed */
41
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
mutex_enter(&kernel_mutex);
140
if (trx->sess == NULL) {
141
/* Open a dummy session */
143
if (!trx_dummy_sess) {
144
trx_dummy_sess = sess_open();
147
trx->sess = trx_dummy_sess;
150
mutex_exit(&kernel_mutex);
152
err = trx_general_rollback_for_mysql(trx, FALSE, NULL);
159
/***********************************************************************
160
Rollback the latest SQL statement for MySQL. */
163
trx_rollback_last_sql_stat_for_mysql(
164
/*=================================*/
165
/* out: error code or DB_SUCCESS */
166
trx_t* trx) /* in: transaction handle */
170
if (trx->conc_state == TRX_NOT_STARTED) {
175
trx->op_info = "rollback of SQL statement";
177
err = trx_general_rollback_for_mysql(trx, TRUE,
178
&(trx->last_sql_stat_start));
179
/* The following call should not be needed, but we play safe: */
180
trx_mark_sql_stat_end(trx);
187
/***********************************************************************
188
Frees savepoint structs. */
191
trx_roll_savepoints_free(
192
/*=====================*/
193
trx_t* trx, /* in: transaction handle */
194
trx_named_savept_t* savep) /* in: free all savepoints > this one;
195
if this is NULL, free all savepoints
198
trx_named_savept_t* next_savep;
201
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
203
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
206
while (savep != NULL) {
207
next_savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
209
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
210
mem_free(savep->name);
217
/***********************************************************************
218
Rolls back a transaction back to a named savepoint. Modifications after the
219
savepoint are undone but InnoDB does NOT release the corresponding locks
220
which are stored in memory. If a lock is 'implicit', that is, a new inserted
221
row holds a lock where the lock information is carried by the trx id stored in
222
the row, these locks are naturally released in the rollback. Savepoints which
223
were set after this savepoint are deleted. */
226
trx_rollback_to_savepoint_for_mysql(
227
/*================================*/
228
/* out: if no savepoint
229
of the name found then
231
otherwise DB_SUCCESS */
232
trx_t* trx, /* in: transaction handle */
233
const char* savepoint_name, /* in: savepoint name */
234
ib_longlong* mysql_binlog_cache_pos) /* out: the MySQL binlog cache
235
position corresponding to this
236
savepoint; MySQL needs this
237
information to remove the
238
binlog entries of the queries
239
executed after the savepoint */
241
trx_named_savept_t* savep;
244
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
246
while (savep != NULL) {
247
if (0 == ut_strcmp(savep->name, savepoint_name)) {
251
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
256
return(DB_NO_SAVEPOINT);
259
if (trx->conc_state == TRX_NOT_STARTED) {
260
ut_print_timestamp(stderr);
261
fputs(" InnoDB: Error: transaction has a savepoint ", stderr);
262
ut_print_name(stderr, trx, FALSE, savep->name);
263
fputs(" though it is not started\n", stderr);
267
/* We can now free all savepoints strictly later than this one */
269
trx_roll_savepoints_free(trx, savep);
271
*mysql_binlog_cache_pos = savep->mysql_binlog_cache_pos;
273
trx->op_info = "rollback to a savepoint";
275
err = trx_general_rollback_for_mysql(trx, TRUE, &(savep->savept));
277
/* Store the current undo_no of the transaction so that we know where
278
to roll back if we have to roll back the next SQL statement: */
280
trx_mark_sql_stat_end(trx);
287
/***********************************************************************
288
Creates a named savepoint. If the transaction is not yet started, starts it.
289
If there is already a savepoint of the same name, this call erases that old
290
savepoint and replaces it with a new. Savepoints are deleted in a transaction
291
commit or rollback. */
294
trx_savepoint_for_mysql(
295
/*====================*/
296
/* out: always DB_SUCCESS */
297
trx_t* trx, /* in: transaction handle */
298
const char* savepoint_name, /* in: savepoint name */
299
ib_longlong binlog_cache_pos) /* in: MySQL binlog cache
300
position corresponding to this
301
connection at the time of the
304
trx_named_savept_t* savep;
307
ut_a(savepoint_name);
309
trx_start_if_not_started(trx);
311
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
313
while (savep != NULL) {
314
if (0 == ut_strcmp(savep->name, savepoint_name)) {
318
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
322
/* There is a savepoint with the same name: free that */
324
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
326
mem_free(savep->name);
330
/* Create a new savepoint and add it as the last in the list */
332
savep = mem_alloc(sizeof(trx_named_savept_t));
334
savep->name = mem_strdup(savepoint_name);
336
savep->savept = trx_savept_take(trx);
338
savep->mysql_binlog_cache_pos = binlog_cache_pos;
340
UT_LIST_ADD_LAST(trx_savepoints, trx->trx_savepoints, savep);
345
/***********************************************************************
346
Releases a named savepoint. Savepoints which
347
were set after this savepoint are deleted. */
350
trx_release_savepoint_for_mysql(
351
/*============================*/
352
/* out: if no savepoint
353
of the name found then
355
otherwise DB_SUCCESS */
356
trx_t* trx, /* in: transaction handle */
357
const char* savepoint_name) /* in: savepoint name */
359
trx_named_savept_t* savep;
361
savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
363
while (savep != NULL) {
364
if (0 == ut_strcmp(savep->name, savepoint_name)) {
368
savep = UT_LIST_GET_NEXT(trx_savepoints, savep);
373
return(DB_NO_SAVEPOINT);
376
/* We can now free all savepoints strictly later than this one */
378
trx_roll_savepoints_free(trx, savep);
380
/* Now we can free this savepoint too */
382
UT_LIST_REMOVE(trx_savepoints, trx->trx_savepoints, savep);
384
mem_free(savep->name);
390
/***********************************************************************
391
Returns a transaction savepoint taken at this point in time. */
397
trx_t* trx) /* in: transaction */
401
savept.least_undo_no = trx->undo_no;
406
/***********************************************************************
407
Rollback or clean up transactions which have no user session. If the
408
transaction already was committed, then we clean up a possible insert
409
undo log. If the transaction was not yet committed, then we roll it back.
410
Note: this is done in a background thread. */
413
trx_rollback_or_clean_all_without_sess(
414
/*===================================*/
415
/* out: a dummy parameter */
416
void* arg __attribute__((unused)))
417
/* in: a dummy parameter required by
423
roll_node_t* roll_node;
426
ib_longlong rows_to_undo;
427
const char* unit = "";
430
mutex_enter(&kernel_mutex);
432
/* Open a dummy session */
434
if (!trx_dummy_sess) {
435
trx_dummy_sess = sess_open();
438
mutex_exit(&kernel_mutex);
440
if (UT_LIST_GET_FIRST(trx_sys->trx_list)) {
443
"InnoDB: Starting in background the rollback"
444
" of uncommitted transactions\n");
449
heap = mem_heap_create(512);
451
mutex_enter(&kernel_mutex);
453
trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
456
if ((trx->sess || (trx->conc_state == TRX_NOT_STARTED))) {
457
trx = UT_LIST_GET_NEXT(trx_list, trx);
458
} else if (trx->conc_state == TRX_PREPARED) {
460
trx->sess = trx_dummy_sess;
461
trx = UT_LIST_GET_NEXT(trx_list, trx);
467
mutex_exit(&kernel_mutex);
470
ut_print_timestamp(stderr);
472
" InnoDB: Rollback of non-prepared transactions"
480
trx->sess = trx_dummy_sess;
482
if (trx->conc_state == TRX_COMMITTED_IN_MEMORY) {
483
fprintf(stderr, "InnoDB: Cleaning up trx with id %lu %lu\n",
484
(ulong) ut_dulint_get_high(trx->id),
485
(ulong) ut_dulint_get_low(trx->id));
487
trx_cleanup_at_db_startup(trx);
494
fork = que_fork_create(NULL, NULL, QUE_FORK_RECOVERY, heap);
497
thr = que_thr_create(fork, heap);
499
roll_node = roll_node_create(heap);
501
thr->child = roll_node;
502
roll_node->common.parent = thr;
504
mutex_enter(&kernel_mutex);
508
ut_a(thr == que_fork_start_command(fork));
510
trx_roll_crash_recv_trx = trx;
511
trx_roll_max_undo_no = ut_conv_dulint_to_longlong(trx->undo_no);
512
trx_roll_progress_printed_pct = 0;
513
rows_to_undo = trx_roll_max_undo_no;
515
if (rows_to_undo > 1000000000) {
516
rows_to_undo = rows_to_undo / 1000000;
520
ut_print_timestamp(stderr);
522
" InnoDB: Rolling back trx with id %lu %lu, %lu%s"
524
(ulong) ut_dulint_get_high(trx->id),
525
(ulong) ut_dulint_get_low(trx->id),
526
(ulong) rows_to_undo, unit);
527
mutex_exit(&kernel_mutex);
529
trx->mysql_thread_id = os_thread_get_curr_id();
531
trx->mysql_process_no = os_proc_get_number();
533
if (trx->dict_operation) {
534
row_mysql_lock_data_dictionary(trx);
537
que_run_threads(thr);
539
mutex_enter(&kernel_mutex);
541
while (trx->que_state != TRX_QUE_RUNNING) {
543
mutex_exit(&kernel_mutex);
546
"InnoDB: Waiting for rollback of trx id %lu to end\n",
547
(ulong) ut_dulint_get_low(trx->id));
548
os_thread_sleep(100000);
550
mutex_enter(&kernel_mutex);
553
mutex_exit(&kernel_mutex);
555
if (trx->dict_operation) {
556
/* If the transaction was for a dictionary operation, we
557
drop the relevant table, if it still exists */
560
"InnoDB: Dropping table with id %lu %lu"
561
" in recovery if it exists\n",
562
(ulong) ut_dulint_get_high(trx->table_id),
563
(ulong) ut_dulint_get_low(trx->table_id));
565
table = dict_table_get_on_id_low(trx->table_id);
568
fputs("InnoDB: Table found: dropping table ", stderr);
569
ut_print_name(stderr, trx, TRUE, table->name);
570
fputs(" in recovery\n", stderr);
572
err = row_drop_table_for_mysql(table->name, trx, TRUE);
574
ut_a(err == (int) DB_SUCCESS);
578
if (trx->dict_operation) {
579
row_mysql_unlock_data_dictionary(trx);
582
fprintf(stderr, "\nInnoDB: Rolling back of trx id %lu %lu completed\n",
583
(ulong) ut_dulint_get_high(trx->id),
584
(ulong) ut_dulint_get_low(trx->id));
587
trx_roll_crash_recv_trx = NULL;
592
/* We count the number of threads in os_thread_exit(). A created
593
thread should always use that to exit and not use return() to exit. */
595
os_thread_exit(NULL);
597
OS_THREAD_DUMMY_RETURN;
600
/***********************************************************************
601
Creates an undo number array. */
604
trx_undo_arr_create(void)
605
/*=====================*/
611
heap = mem_heap_create(1024);
613
arr = mem_heap_alloc(heap, sizeof(trx_undo_arr_t));
615
arr->infos = mem_heap_alloc(heap, sizeof(trx_undo_inf_t)
616
* UNIV_MAX_PARALLELISM);
617
arr->n_cells = UNIV_MAX_PARALLELISM;
622
for (i = 0; i < UNIV_MAX_PARALLELISM; i++) {
624
(trx_undo_arr_get_nth_info(arr, i))->in_use = FALSE;
630
/***********************************************************************
631
Frees an undo number array. */
636
trx_undo_arr_t* arr) /* in: undo number array */
638
ut_ad(arr->n_used == 0);
640
mem_heap_free(arr->heap);
643
/***********************************************************************
644
Stores info of an undo log record to the array if it is not stored yet. */
647
trx_undo_arr_store_info(
648
/*====================*/
649
/* out: FALSE if the record already existed in the
651
trx_t* trx, /* in: transaction */
652
dulint undo_no)/* in: undo number */
654
trx_undo_inf_t* cell;
655
trx_undo_inf_t* stored_here;
662
arr = trx->undo_no_arr;
663
n_used = arr->n_used;
667
cell = trx_undo_arr_get_nth_info(arr, i);
671
/* Not in use, we may store here */
672
cell->undo_no = undo_no;
682
if (0 == ut_dulint_cmp(cell->undo_no, undo_no)) {
685
stored_here->in_use = FALSE;
686
ut_ad(arr->n_used > 0);
690
ut_ad(arr->n_used == n_used);
696
if (n == n_used && stored_here) {
698
ut_ad(arr->n_used == 1 + n_used);
705
/***********************************************************************
706
Removes an undo number from the array. */
709
trx_undo_arr_remove_info(
710
/*=====================*/
711
trx_undo_arr_t* arr, /* in: undo number array */
712
dulint undo_no)/* in: undo number */
714
trx_undo_inf_t* cell;
719
n_used = arr->n_used;
723
cell = trx_undo_arr_get_nth_info(arr, i);
726
&& 0 == ut_dulint_cmp(cell->undo_no, undo_no)) {
728
cell->in_use = FALSE;
730
ut_ad(arr->n_used > 0);
739
/***********************************************************************
740
Gets the biggest undo number in an array. */
743
trx_undo_arr_get_biggest(
744
/*=====================*/
745
/* out: biggest value, ut_dulint_zero if
746
the array is empty */
747
trx_undo_arr_t* arr) /* in: undo number array */
749
trx_undo_inf_t* cell;
756
n_used = arr->n_used;
757
biggest = ut_dulint_zero;
760
cell = trx_undo_arr_get_nth_info(arr, i);
764
if (ut_dulint_cmp(cell->undo_no, biggest) > 0) {
766
biggest = cell->undo_no;
776
/***************************************************************************
777
Tries truncate the undo logs. */
780
trx_roll_try_truncate(
781
/*==================*/
782
trx_t* trx) /* in: transaction */
788
ut_ad(mutex_own(&(trx->undo_mutex)));
789
ut_ad(mutex_own(&((trx->rseg)->mutex)));
791
trx->pages_undone = 0;
793
arr = trx->undo_no_arr;
795
limit = trx->undo_no;
797
if (arr->n_used > 0) {
798
biggest = trx_undo_arr_get_biggest(arr);
800
if (ut_dulint_cmp(biggest, limit) >= 0) {
802
limit = ut_dulint_add(biggest, 1);
806
if (trx->insert_undo) {
807
trx_undo_truncate_end(trx, trx->insert_undo, limit);
810
if (trx->update_undo) {
811
trx_undo_truncate_end(trx, trx->update_undo, limit);
815
/***************************************************************************
816
Pops the topmost undo log record in a single undo log and updates the info
817
about the topmost record in the undo log memory struct. */
820
trx_roll_pop_top_rec(
821
/*=================*/
822
/* out: undo log record, the page s-latched */
823
trx_t* trx, /* in: transaction */
824
trx_undo_t* undo, /* in: undo log */
825
mtr_t* mtr) /* in: mtr */
829
trx_undo_rec_t* prev_rec;
830
page_t* prev_rec_page;
832
ut_ad(mutex_own(&(trx->undo_mutex)));
834
undo_page = trx_undo_page_get_s_latched(undo->space,
835
undo->top_page_no, mtr);
836
offset = undo->top_offset;
838
/* fprintf(stderr, "Thread %lu undoing trx %lu undo record %lu\n",
839
os_thread_get_curr_id(), ut_dulint_get_low(trx->id),
840
ut_dulint_get_low(undo->top_undo_no)); */
842
prev_rec = trx_undo_get_prev_rec(undo_page + offset,
843
undo->hdr_page_no, undo->hdr_offset,
845
if (prev_rec == NULL) {
849
prev_rec_page = buf_frame_align(prev_rec);
851
if (prev_rec_page != undo_page) {
856
undo->top_page_no = buf_frame_get_page_no(prev_rec_page);
857
undo->top_offset = prev_rec - prev_rec_page;
858
undo->top_undo_no = trx_undo_rec_get_undo_no(prev_rec);
861
return(undo_page + offset);
864
/************************************************************************
865
Pops the topmost record when the two undo logs of a transaction are seen
866
as a single stack of records ordered by their undo numbers. Inserts the
867
undo number of the popped undo record to the array of currently processed
868
undo numbers in the transaction. When the query thread finishes processing
869
of this undo record, it must be released with trx_undo_rec_release. */
872
trx_roll_pop_top_rec_of_trx(
873
/*========================*/
874
/* out: undo log record copied to heap, NULL
875
if none left, or if the undo number of the
876
top record would be less than the limit */
877
trx_t* trx, /* in: transaction */
878
dulint limit, /* in: least undo number we need */
879
dulint* roll_ptr,/* out: roll pointer to undo record */
880
mem_heap_t* heap) /* in: memory heap where copied */
883
trx_undo_t* ins_undo;
884
trx_undo_t* upd_undo;
885
trx_undo_rec_t* undo_rec;
886
trx_undo_rec_t* undo_rec_copy;
895
mutex_enter(&(trx->undo_mutex));
897
if (trx->pages_undone >= TRX_ROLL_TRUNC_THRESHOLD) {
898
mutex_enter(&(rseg->mutex));
900
trx_roll_try_truncate(trx);
902
mutex_exit(&(rseg->mutex));
905
ins_undo = trx->insert_undo;
906
upd_undo = trx->update_undo;
908
if (!ins_undo || ins_undo->empty) {
910
} else if (!upd_undo || upd_undo->empty) {
912
} else if (ut_dulint_cmp(upd_undo->top_undo_no,
913
ins_undo->top_undo_no) > 0) {
919
if (!undo || undo->empty
920
|| (ut_dulint_cmp(limit, undo->top_undo_no) > 0)) {
922
if ((trx->undo_no_arr)->n_used == 0) {
923
/* Rollback is ending */
925
mutex_enter(&(rseg->mutex));
927
trx_roll_try_truncate(trx);
929
mutex_exit(&(rseg->mutex));
932
mutex_exit(&(trx->undo_mutex));
937
if (undo == ins_undo) {
943
*roll_ptr = trx_undo_build_roll_ptr(is_insert, (undo->rseg)->id,
948
undo_rec = trx_roll_pop_top_rec(trx, undo, &mtr);
950
undo_no = trx_undo_rec_get_undo_no(undo_rec);
952
ut_ad(ut_dulint_cmp(ut_dulint_add(undo_no, 1), trx->undo_no) == 0);
954
/* We print rollback progress info if we are in a crash recovery
955
and the transaction has at least 1000 row operations to undo. */
957
if (trx == trx_roll_crash_recv_trx && trx_roll_max_undo_no > 1000) {
959
progress_pct = 100 - (ulint)
960
((ut_conv_dulint_to_longlong(undo_no) * 100)
961
/ trx_roll_max_undo_no);
962
if (progress_pct != trx_roll_progress_printed_pct) {
963
if (trx_roll_progress_printed_pct == 0) {
965
"\nInnoDB: Progress in percents:"
966
" %lu", (ulong) progress_pct);
969
" %lu", (ulong) progress_pct);
972
trx_roll_progress_printed_pct = progress_pct;
976
trx->undo_no = undo_no;
978
if (!trx_undo_arr_store_info(trx, undo_no)) {
979
/* A query thread is already processing this undo log record */
981
mutex_exit(&(trx->undo_mutex));
988
undo_rec_copy = trx_undo_rec_copy(undo_rec, heap);
990
mutex_exit(&(trx->undo_mutex));
994
return(undo_rec_copy);
997
/************************************************************************
998
Reserves an undo log record for a query thread to undo. This should be
999
called if the query thread gets the undo log record not using the pop
1003
trx_undo_rec_reserve(
1004
/*=================*/
1005
/* out: TRUE if succeeded */
1006
trx_t* trx, /* in: transaction */
1007
dulint undo_no)/* in: undo number of the record */
1011
mutex_enter(&(trx->undo_mutex));
1013
ret = trx_undo_arr_store_info(trx, undo_no);
1015
mutex_exit(&(trx->undo_mutex));
1020
/***********************************************************************
1021
Releases a reserved undo record. */
1024
trx_undo_rec_release(
1025
/*=================*/
1026
trx_t* trx, /* in: transaction */
1027
dulint undo_no)/* in: undo number */
1029
trx_undo_arr_t* arr;
1031
mutex_enter(&(trx->undo_mutex));
1033
arr = trx->undo_no_arr;
1035
trx_undo_arr_remove_info(arr, undo_no);
1037
mutex_exit(&(trx->undo_mutex));
1040
/*************************************************************************
1041
Starts a rollback operation. */
1046
trx_t* trx, /* in: transaction */
1047
trx_sig_t* sig, /* in: signal starting the rollback */
1048
que_thr_t** next_thr)/* in/out: next query thread to run;
1049
if the value which is passed in is
1050
a pointer to a NULL pointer, then the
1051
calling function can start running
1052
a new query thread; if the passed value is
1053
NULL, the parameter is ignored */
1057
/* que_thr_t* thr2; */
1059
ut_ad(mutex_own(&kernel_mutex));
1060
ut_ad((trx->undo_no_arr == NULL) || ((trx->undo_no_arr)->n_used == 0));
1062
/* Initialize the rollback field in the transaction */
1064
if (sig->type == TRX_SIG_TOTAL_ROLLBACK) {
1066
trx->roll_limit = ut_dulint_zero;
1068
} else if (sig->type == TRX_SIG_ROLLBACK_TO_SAVEPT) {
1070
trx->roll_limit = (sig->savept).least_undo_no;
1072
} else if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1074
trx->roll_limit = trx->last_sql_stat_start.least_undo_no;
1079
ut_a(ut_dulint_cmp(trx->roll_limit, trx->undo_no) <= 0);
1081
trx->pages_undone = 0;
1083
if (trx->undo_no_arr == NULL) {
1084
trx->undo_no_arr = trx_undo_arr_create();
1087
/* Build a 'query' graph which will perform the undo operations */
1089
roll_graph = trx_roll_graph_build(trx);
1091
trx->graph = roll_graph;
1092
trx->que_state = TRX_QUE_ROLLING_BACK;
1094
thr = que_fork_start_command(roll_graph);
1098
/* thr2 = que_fork_start_command(roll_graph);
1102
if (next_thr && (*next_thr == NULL)) {
1104
/* srv_que_task_enqueue_low(thr2); */
1106
srv_que_task_enqueue_low(thr);
1107
/* srv_que_task_enqueue_low(thr2); */
1111
/********************************************************************
1112
Builds an undo 'query' graph for a transaction. The actual rollback is
1113
performed by executing this query graph like a query subprocedure call.
1114
The reply about the completion of the rollback will be sent by this
1118
trx_roll_graph_build(
1119
/*=================*/
1120
/* out, own: the query graph */
1121
trx_t* trx) /* in: trx handle */
1126
/* que_thr_t* thr2; */
1128
ut_ad(mutex_own(&kernel_mutex));
1130
heap = mem_heap_create(512);
1131
fork = que_fork_create(NULL, NULL, QUE_FORK_ROLLBACK, heap);
1134
thr = que_thr_create(fork, heap);
1135
/* thr2 = que_thr_create(fork, heap); */
1137
thr->child = row_undo_node_create(trx, thr, heap);
1138
/* thr2->child = row_undo_node_create(trx, thr2, heap); */
1143
/*************************************************************************
1144
Finishes error processing after the necessary partial rollback has been
1148
trx_finish_error_processing(
1149
/*========================*/
1150
trx_t* trx) /* in: transaction */
1153
trx_sig_t* next_sig;
1155
ut_ad(mutex_own(&kernel_mutex));
1157
sig = UT_LIST_GET_FIRST(trx->signals);
1159
while (sig != NULL) {
1160
next_sig = UT_LIST_GET_NEXT(signals, sig);
1162
if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1164
trx_sig_remove(trx, sig);
1170
trx->que_state = TRX_QUE_RUNNING;
1173
/*************************************************************************
1174
Finishes a partial rollback operation. */
1177
trx_finish_partial_rollback_off_kernel(
1178
/*===================================*/
1179
trx_t* trx, /* in: transaction */
1180
que_thr_t** next_thr)/* in/out: next query thread to run;
1181
if the value which is passed in is a pointer
1182
to a NULL pointer, then the calling function
1183
can start running a new query thread; if this
1184
parameter is NULL, it is ignored */
1188
ut_ad(mutex_own(&kernel_mutex));
1190
sig = UT_LIST_GET_FIRST(trx->signals);
1192
/* Remove the signal from the signal queue and send reply message
1195
trx_sig_reply(sig, next_thr);
1196
trx_sig_remove(trx, sig);
1198
trx->que_state = TRX_QUE_RUNNING;
1201
/********************************************************************
1202
Finishes a transaction rollback. */
1205
trx_finish_rollback_off_kernel(
1206
/*===========================*/
1207
que_t* graph, /* in: undo graph which can now be freed */
1208
trx_t* trx, /* in: transaction */
1209
que_thr_t** next_thr)/* in/out: next query thread to run;
1210
if the value which is passed in is
1211
a pointer to a NULL pointer, then the
1212
calling function can start running
1213
a new query thread; if this parameter is
1214
NULL, it is ignored */
1217
trx_sig_t* next_sig;
1219
ut_ad(mutex_own(&kernel_mutex));
1221
ut_a(trx->undo_no_arr == NULL || trx->undo_no_arr->n_used == 0);
1223
/* Free the memory reserved by the undo graph */
1224
que_graph_free(graph);
1226
sig = UT_LIST_GET_FIRST(trx->signals);
1228
if (sig->type == TRX_SIG_ROLLBACK_TO_SAVEPT) {
1230
trx_finish_partial_rollback_off_kernel(trx, next_thr);
1234
} else if (sig->type == TRX_SIG_ERROR_OCCURRED) {
1236
trx_finish_error_processing(trx);
1242
if (lock_print_waits) {
1243
fprintf(stderr, "Trx %lu rollback finished\n",
1244
(ulong) ut_dulint_get_low(trx->id));
1246
#endif /* UNIV_DEBUG */
1248
trx_commit_off_kernel(trx);
1250
/* Remove all TRX_SIG_TOTAL_ROLLBACK signals from the signal queue and
1251
send reply messages to them */
1253
trx->que_state = TRX_QUE_RUNNING;
1255
while (sig != NULL) {
1256
next_sig = UT_LIST_GET_NEXT(signals, sig);
1258
if (sig->type == TRX_SIG_TOTAL_ROLLBACK) {
1260
trx_sig_reply(sig, next_thr);
1262
trx_sig_remove(trx, sig);
1269
/*************************************************************************
1270
Creates a rollback command node struct. */
1275
/* out, own: rollback node struct */
1276
mem_heap_t* heap) /* in: mem heap where created */
1280
node = mem_heap_alloc(heap, sizeof(roll_node_t));
1281
node->common.type = QUE_NODE_ROLLBACK;
1282
node->state = ROLL_NODE_SEND;
1284
node->partial = FALSE;
1289
/***************************************************************
1290
Performs an execution step for a rollback command node in a query graph. */
1295
/* out: query thread to run next, or NULL */
1296
que_thr_t* thr) /* in: query thread */
1300
trx_savept_t* savept;
1302
node = thr->run_node;
1304
ut_ad(que_node_get_type(node) == QUE_NODE_ROLLBACK);
1306
if (thr->prev_node == que_node_get_parent(node)) {
1307
node->state = ROLL_NODE_SEND;
1310
if (node->state == ROLL_NODE_SEND) {
1311
mutex_enter(&kernel_mutex);
1313
node->state = ROLL_NODE_WAIT;
1315
if (node->partial) {
1316
sig_no = TRX_SIG_ROLLBACK_TO_SAVEPT;
1317
savept = &(node->savept);
1319
sig_no = TRX_SIG_TOTAL_ROLLBACK;
1323
/* Send a rollback signal to the transaction */
1325
trx_sig_send(thr_get_trx(thr), sig_no, TRX_SIG_SELF, thr,
1328
thr->state = QUE_THR_SIG_REPLY_WAIT;
1330
mutex_exit(&kernel_mutex);
1335
ut_ad(node->state == ROLL_NODE_WAIT);
1337
thr->run_node = que_node_get_parent(node);