diff --git a/src/lock_tree/locktree.c b/src/lock_tree/locktree.c index 3ca9d9b5ea9..95308fbdb87 100644 --- a/src/lock_tree/locktree.c +++ b/src/lock_tree/locktree.c @@ -1332,8 +1332,10 @@ toku_lt_create(toku_lock_tree** ptree, toku_ltm_incr_lock_memory, toku_ltm_decr_lock_memory, mgr); if (r != 0) goto cleanup; - r = toku_rth_create(&tmp_tree->rth); + if (r != 0) + goto cleanup; + r = toku_rth_create(&tmp_tree->txns_to_unlock); if (r != 0) goto cleanup; @@ -1362,6 +1364,8 @@ cleanup: toku_rt_close(tmp_tree->borderwrite); if (tmp_tree->rth) toku_rth_close(tmp_tree->rth); + if (tmp_tree->txns_to_unlock) + toku_rth_close(tmp_tree->txns_to_unlock); if (tmp_tree->buf) toku_free(tmp_tree->buf); if (tmp_tree->bw_buf) @@ -1394,6 +1398,7 @@ toku_lt_set_dict_id(toku_lock_tree* lt, DICTIONARY_ID dict_id) { static void lt_add_db(toku_lock_tree* tree, DB *db); static void lt_remove_db(toku_lock_tree* tree, DB *db); +static void lt_unlock_deferred_txns(toku_lock_tree *tree); int toku_ltm_get_lt(toku_ltm* mgr, toku_lock_tree** ptree, DICTIONARY_ID dict_id, DB *db, toku_dbt_cmp compare_fun) { @@ -1414,6 +1419,7 @@ toku_ltm_get_lt(toku_ltm* mgr, toku_lock_tree** ptree, DICTIONARY_ID dict_id, DB assert (tree != NULL); toku_lt_add_ref(tree); lt_add_db(tree, db); + lt_unlock_deferred_txns(tree); *ptree = tree; r = 0; goto cleanup; @@ -1484,17 +1490,23 @@ toku_lt_close(toku_lock_tree* tree) { if (!first_error && r != 0) first_error = r; + uint32_t ranges = 0; toku_rth_start_scan(tree->rth); rt_forest* forest; - while ((forest = toku_rth_next(tree->rth)) != NULL) { + if (forest->self_read) + ranges += toku_rt_get_size(forest->self_read); r = lt_free_contents(tree, forest->self_read); if (!first_error && r != 0) first_error = r; + if (forest->self_write) + ranges += toku_rt_get_size(forest->self_write); r = lt_free_contents(tree, forest->self_write); if (!first_error && r != 0) first_error = r; } + ltm_decr_locks(tree->mgr, ranges); + toku_rth_close(tree->txns_to_unlock); toku_rth_close(tree->rth); toku_omt_destroy(&tree->dbs); toku_mutex_destroy(&tree->mutex); @@ -2112,34 +2124,26 @@ lt_unlock_txn(toku_lock_tree* tree, TXNID txn) { uint32_t ranges = 0; if (selfread) { - uint32_t size = toku_rt_get_size(selfread); - ranges += size; + ranges += toku_rt_get_size(selfread); r = lt_free_contents(tree, selfread); if (r != 0) return lt_panic(tree, r); } if (selfwrite) { - uint32_t size = toku_rt_get_size(selfwrite); - ranges += size; + ranges += toku_rt_get_size(selfwrite); - // get a db from the db's associated with the lock tree and use it to update the borderwrite - // if there are no db's, then assume that the db was closed before all transactions that referenced the - // lock tree were retired. in this case, there is no need to update the border write since - // these transactions may no longer do anything to the db since it is closed, and - // we expect to just close the lock tree when all of the open references to it are retired. - if (toku_omt_size(tree->dbs) > 0) { - OMTVALUE dbv; - r = toku_omt_fetch(tree->dbs, 0, &dbv); - assert_zero(r); - DB *db = dbv; + assert(toku_omt_size(tree->dbs) > 0); + OMTVALUE dbv; + r = toku_omt_fetch(tree->dbs, 0, &dbv); + assert_zero(r); + DB *db = dbv; + lt_set_comparison_functions(tree, db); + r = lt_border_delete(tree, selfwrite); + lt_clear_comparison_functions(tree); + if (r != 0) + return lt_panic(tree, r); - lt_set_comparison_functions(tree, db); - r = lt_border_delete(tree, selfwrite); - lt_clear_comparison_functions(tree); - if (r != 0) - return lt_panic(tree, r); - } r = lt_free_contents(tree, selfwrite); if (r != 0) return lt_panic(tree, r); @@ -2166,13 +2170,29 @@ toku_lt_unlock_txn(toku_lock_tree* tree, TXNID txn) { r = EINVAL; goto cleanup; } toku_mutex_lock(&tree->mutex); - lt_unlock_txn(tree, txn); - lt_retry_lock_requests(tree); + if (toku_omt_size(tree->dbs) > 0) { + lt_unlock_txn(tree, txn); + lt_retry_lock_requests(tree); + } else { + r = toku_rth_insert(tree->txns_to_unlock, txn); + } toku_mutex_unlock(&tree->mutex); cleanup: return r; } +static void +lt_unlock_deferred_txns(toku_lock_tree *tree) { + toku_rth_start_scan(tree->txns_to_unlock); + rt_forest *forest; + while ((forest = toku_rth_next(tree->txns_to_unlock)) != NULL) { + TXNID txn = forest->hash_key; + lt_unlock_txn(tree, txn); + } + toku_rth_clear(tree->txns_to_unlock); + lt_retry_lock_requests(tree); +} + void toku_lt_add_ref(toku_lock_tree* tree) { assert(tree); diff --git a/src/lock_tree/locktree.h b/src/lock_tree/locktree.h index 361f871400d..aec717655f0 100644 --- a/src/lock_tree/locktree.h +++ b/src/lock_tree/locktree.h @@ -76,6 +76,7 @@ struct __toku_lock_tree { OMT dbs; //The extant dbs using this lock tree. OMT lock_requests; toku_pthread_mutex_t mutex; + toku_rth* txns_to_unlock; // set of txn's that could not release their locks because there was no db for the comparison function /** A temporary area where we store the results of various find on the range trees that this lock tree owns diff --git a/src/lock_tree/tests/test_close_lock_conflict.c b/src/lock_tree/tests/test_close_lock_conflict.c new file mode 100644 index 00000000000..520692c4e6d --- /dev/null +++ b/src/lock_tree/tests/test_close_lock_conflict.c @@ -0,0 +1,96 @@ +// verify that the lock tree is maintained after closes if txn's still own locks + +// A gets W(L) +// B gets W(M) +// close lock tree +// A unlocks +// reopen lock tree +// B gets W(L) +// B unlocks + +#include "test.h" + +int main(int argc, const char *argv[]) { + int r; + + uint32_t max_locks = 2; + uint64_t max_lock_memory = 4096; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { + verbose++; + continue; + } + if (strcmp(argv[i], "-q") == 0 || strcmp(argv[i], "--quiet") == 0) { + if (verbose > 0) verbose--; + continue; + } + if (strcmp(argv[i], "--max_locks") == 0 && i+1 < argc) { + max_locks = atoi(argv[++i]); + continue; + } + if (strcmp(argv[i], "--max_lock_memory") == 0 && i+1 < argc) { + max_lock_memory = atoi(argv[++i]); + continue; + } + assert(0); + } + + // setup + toku_ltm *ltm = NULL; + r = toku_ltm_create(<m, max_locks, max_lock_memory, dbpanic); + assert(r == 0 && ltm); + + DB *fake_db = (DB *) 1; + + toku_lock_tree *lt = NULL; + r = toku_ltm_get_lt(ltm, <, (DICTIONARY_ID){1}, fake_db, dbcmp); + assert(r == 0 && lt); + + const TXNID txn_a = 1; + const TXNID txn_b = 2; + + DBT key_l = { .data = "L", .size = 1 }; + DBT key_m = { .data = "M", .size = 1 }; + + // txn_a gets W(L) + toku_lock_request a_w_l; toku_lock_request_init(&a_w_l, fake_db, txn_a, &key_l, &key_l, LOCK_REQUEST_WRITE); + r = toku_lock_request_start(&a_w_l, lt, false); assert(r == 0); + assert(a_w_l.state == LOCK_REQUEST_COMPLETE && a_w_l.complete_r == 0); + toku_lock_request_destroy(&a_w_l); + toku_lt_add_ref(lt); + + // txn_b gets W(M) + toku_lock_request b_w_m; toku_lock_request_init(&b_w_m, fake_db, txn_b, &key_m, &key_m, LOCK_REQUEST_WRITE); + r = toku_lock_request_start(&b_w_m, lt, false); assert(r == 0); + assert(b_w_m.state == LOCK_REQUEST_COMPLETE && b_w_m.complete_r == 0); + toku_lock_request_destroy(&b_w_m); + toku_lt_add_ref(lt); + + // start closing the lock tree + toku_lt_remove_db_ref(lt, fake_db); + + // txn_a unlocks + r = toku_lt_unlock_txn(lt, txn_a); + toku_lt_remove_ref(lt); + + // reopen the lock tree + r = toku_ltm_get_lt(ltm, <, (DICTIONARY_ID){1}, fake_db, dbcmp); + assert(r == 0 && lt); + + // txn_b gets W(L) + toku_lock_request b_w_l; toku_lock_request_init(&b_w_l, fake_db, txn_b, &key_l, &key_l, LOCK_REQUEST_WRITE); + r = toku_lock_request_start(&b_w_l, lt, false); assert(r == 0); + assert(b_w_l.state == LOCK_REQUEST_COMPLETE && b_w_l.complete_r == 0); + toku_lock_request_destroy(&b_w_l); + toku_lt_add_ref(lt); + + // release all locks for the transaction + r = toku_lt_unlock_txn(lt, txn_b); assert(r == 0); + toku_lt_remove_ref(lt); + + // shutdown + r = toku_ltm_close(ltm); assert(r == 0); + + return 0; +} diff --git a/src/lock_tree/tests/test_unlock_after_close.c b/src/lock_tree/tests/test_unlock_after_close.c index a48f5c87510..5f04357eb03 100644 --- a/src/lock_tree/tests/test_unlock_after_close.c +++ b/src/lock_tree/tests/test_unlock_after_close.c @@ -1,4 +1,8 @@ -// add a write lock on L for a transaction, remove a reference on the lock tree, then release the locks for the transaction. +// verify that txn's can release locks after the lock tree is closed + +// A gets W(L) +// close lock tree +// A unlocks #include "test.h" @@ -55,6 +59,7 @@ int main(int argc, const char *argv[]) { // release all locks for the transaction r = toku_lt_unlock_txn(lt, txn_a); assert(r == 0); + toku_lt_remove_ref(lt); // shutdown r = toku_ltm_close(ltm); assert(r == 0);