diff --git a/src/box/memtx_engine.c b/src/box/memtx_engine.c index 8bf90b6012c5f4324301eaf087c5b55387995cb7..59ad16823681c70b058bd3770553258857aaf060 100644 --- a/src/box/memtx_engine.c +++ b/src/box/memtx_engine.c @@ -369,15 +369,6 @@ memtx_engine_rollback_statement(struct engine *engine, struct txn *txn, tuple_unref(stmt->new_tuple); } -static void -memtx_engine_rollback(struct engine *engine, struct txn *txn) -{ - struct txn_stmt *stmt; - stailq_reverse(&txn->stmts); - stailq_foreach_entry(stmt, &txn->stmts, next) - memtx_engine_rollback_statement(engine, txn, stmt); -} - static int memtx_engine_bootstrap(struct engine *engine) { @@ -862,7 +853,7 @@ static const struct engine_vtab memtx_engine_vtab = { /* .prepare = */ generic_engine_prepare, /* .commit = */ generic_engine_commit, /* .rollback_statement = */ memtx_engine_rollback_statement, - /* .rollback = */ memtx_engine_rollback, + /* .rollback = */ generic_engine_rollback, /* .switch_to_ro = */ generic_engine_switch_to_ro, /* .bootstrap = */ memtx_engine_bootstrap, /* .begin_initial_recovery = */ memtx_engine_begin_initial_recovery, diff --git a/src/box/txn.c b/src/box/txn.c index 9599284bc578cd4e7d36022f885261777c434dac..53ebfc05354218221059c493362078bf26891732 100644 --- a/src/box/txn.c +++ b/src/box/txn.c @@ -116,30 +116,36 @@ txn_stmt_unref_tuples(struct txn_stmt *stmt) tuple_unref(stmt->new_tuple); } +/* + * Undo changes done by a statement and run the corresponding + * rollback triggers. + * + * Note, a trigger set by a particular statement must be run right + * after the statement is rolled back, because rollback triggers + * installed by DDL statements restore the schema cache, which is + * necessary to roll back previous statements. For example, to roll + * back a DML statement applied to a space whose index is dropped + * later in the same transaction, we must restore the dropped index + * first. + */ +static void +txn_rollback_one_stmt(struct txn *txn, struct txn_stmt *stmt) +{ + if (txn->engine != NULL && stmt->space != NULL) + engine_rollback_statement(txn->engine, txn, stmt); + if (stmt->has_triggers) + txn_run_rollback_triggers(txn, &stmt->on_rollback); +} + static void txn_rollback_to_svp(struct txn *txn, struct stailq_entry *svp) { - /* - * Undo changes done to the database by the rolled back - * statements and collect corresponding rollback triggers. - * Don't release the tuples as rollback triggers might - * still need them. - */ struct txn_stmt *stmt; struct stailq rollback; - RLIST_HEAD(on_rollback); stailq_cut_tail(&txn->stmts, svp, &rollback); stailq_reverse(&rollback); stailq_foreach_entry(stmt, &rollback, next) { - /* - * Note the statement list is reversed hence - * we must append triggers to the tail so as - * to preserve the rollback order. - */ - if (stmt->has_triggers) - rlist_splice_tail(&on_rollback, &stmt->on_rollback); - if (txn->engine != NULL && stmt->space != NULL) - engine_rollback_statement(txn->engine, txn, stmt); + txn_rollback_one_stmt(txn, stmt); if (stmt->row != NULL && stmt->row->replica_id == 0) { assert(txn->n_new_rows > 0); txn->n_new_rows--; @@ -150,15 +156,10 @@ txn_rollback_to_svp(struct txn *txn, struct stailq_entry *svp) assert(txn->n_applier_rows > 0); txn->n_applier_rows--; } + txn_stmt_unref_tuples(stmt); stmt->space = NULL; stmt->row = NULL; } - /* Run rollback triggers installed after the savepoint. */ - txn_run_rollback_triggers(txn, &on_rollback); - - /* Release the tuples. */ - stailq_foreach_entry(stmt, &rollback, next) - txn_stmt_unref_tuples(stmt); } /* @@ -420,6 +421,10 @@ txn_complete(struct txn *txn) */ if (txn->signature < 0) { /* Undo the transaction. */ + struct txn_stmt *stmt; + stailq_reverse(&txn->stmts); + stailq_foreach_entry(stmt, &txn->stmts, next) + txn_rollback_one_stmt(txn, stmt); if (txn->engine) engine_rollback(txn->engine, txn); if (txn_has_flag(txn, TXN_HAS_TRIGGERS)) @@ -494,7 +499,6 @@ txn_write_to_wal(struct txn *txn) if (stmt->has_triggers) { txn_init_triggers(txn); rlist_splice(&txn->on_commit, &stmt->on_commit); - rlist_splice(&txn->on_rollback, &stmt->on_rollback); } if (stmt->row == NULL) continue; /* A read (e.g. select) request */ @@ -619,13 +623,6 @@ txn_rollback(struct txn *txn) trigger_clear(&txn->fiber_on_stop); if (!txn_has_flag(txn, TXN_CAN_YIELD)) trigger_clear(&txn->fiber_on_yield); - struct txn_stmt *stmt; - stailq_foreach_entry(stmt, &txn->stmts, next) { - if (stmt->has_triggers) { - txn_init_triggers(txn); - rlist_splice(&txn->on_rollback, &stmt->on_rollback); - } - } txn->signature = -1; txn_complete(txn); fiber_set_txn(fiber(), NULL); diff --git a/src/box/vy_tx.c b/src/box/vy_tx.c index d0e9206880e52dbc7de2c5711ee0b037c4d0027d..9b300fde692d5028129eb4ffe59ec201b0c96461 100644 --- a/src/box/vy_tx.c +++ b/src/box/vy_tx.c @@ -908,7 +908,8 @@ vy_tx_begin_statement(struct vy_tx *tx, struct space *space, void **savepoint) void vy_tx_rollback_statement(struct vy_tx *tx, void *svp) { - if (tx->state == VINYL_TX_ABORT) + if (tx->state == VINYL_TX_ABORT || + tx->state == VINYL_TX_COMMIT) return; assert(tx->state == VINYL_TX_READY); diff --git a/test/box/transaction.result b/test/box/transaction.result index 1ed649d26ff9f70a0ad5fc336a45366923fe70fa..718d74228780fc698518298bc47b12b61a71ac59 100644 --- a/test/box/transaction.result +++ b/test/box/transaction.result @@ -817,3 +817,30 @@ box.begin() box.execute([[CREATE TABLE test(id INTEGER PRIMARY KEY AUTOINCREMENT box.rollback() --- ... +-- +-- gh-4368: transaction rollback leads to a crash if DDL and DML statements +-- are mixed in the same transaction. +-- +test_run:cmd("setopt delimiter ';'") +--- +- true +... +for i = 1, 100 do + box.begin() + s = box.schema.space.create('test') + s:create_index('pk') + s:create_index('sk', {unique = true, parts = {2, 'unsigned'}}) + s:insert{1, 1} + s.index.sk:alter{unique = false} + s:insert{2, 1} + s.index.sk:drop() + s:delete{2} + box.rollback() + fiber.sleep(0) +end; +--- +... +test_run:cmd("setopt delimiter ''"); +--- +- true +... diff --git a/test/box/transaction.test.lua b/test/box/transaction.test.lua index a0014c9f5e5fc2358df574c21037bad6835de105..e057483f696de0f36112fc1487f80ad0905b2014 100644 --- a/test/box/transaction.test.lua +++ b/test/box/transaction.test.lua @@ -438,3 +438,23 @@ check1, check2, check3, check4 -- box.begin() box.execute([[CREATE TABLE test(id INTEGER PRIMARY KEY AUTOINCREMENT)]]) fiber.yield() box.rollback() + +-- +-- gh-4368: transaction rollback leads to a crash if DDL and DML statements +-- are mixed in the same transaction. +-- +test_run:cmd("setopt delimiter ';'") +for i = 1, 100 do + box.begin() + s = box.schema.space.create('test') + s:create_index('pk') + s:create_index('sk', {unique = true, parts = {2, 'unsigned'}}) + s:insert{1, 1} + s.index.sk:alter{unique = false} + s:insert{2, 1} + s.index.sk:drop() + s:delete{2} + box.rollback() + fiber.sleep(0) +end; +test_run:cmd("setopt delimiter ''");