[BUGFIX] Fix the bug about unlock failed, which due to replay disorder
This commit is contained in:
parent
364fdb8222
commit
829ddcb041
@ -299,6 +299,220 @@ TEST_F(TestObjLock, out_trans_lock)
|
|||||||
ASSERT_EQ(OB_SUCCESS, ret);
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(TestObjLock, out_trans_unlock_twice)
|
||||||
|
{
|
||||||
|
LOG_INFO("TestObjLock::out_trans_lock");
|
||||||
|
// TEST SET
|
||||||
|
// 1. UNLOCK AFTER A COMMITTED UNLOCK
|
||||||
|
// 2. UNLOCK AFTER A REPLAY AND COMMITTED UNLOCK
|
||||||
|
// 3. UNLCOK AFTER A REPLAY AND UNCOMMITTED UNLOCK
|
||||||
|
int ret = OB_SUCCESS;
|
||||||
|
bool is_try_lock = true;
|
||||||
|
int64_t expired_time = 0;
|
||||||
|
share::SCN min_commited_scn;
|
||||||
|
share::SCN flushed_scn;
|
||||||
|
share::SCN commit_version;
|
||||||
|
share::SCN commit_scn;
|
||||||
|
ObTxIDSet conflict_tx_set;
|
||||||
|
unsigned char lock_mode_in_same_trans = 0x0;
|
||||||
|
ObLockParam param;
|
||||||
|
|
||||||
|
MyTxCtx default_ctx;
|
||||||
|
ObStoreCtx store_ctx;
|
||||||
|
|
||||||
|
min_commited_scn.set_min();
|
||||||
|
flushed_scn.set_min();
|
||||||
|
start_tx(DEFAULT_TRANS_ID, default_ctx);
|
||||||
|
get_store_ctx(default_ctx, store_ctx);
|
||||||
|
|
||||||
|
// 1. UNLOCK AFTER A COMMON UNLOCK
|
||||||
|
// The complete progress is:
|
||||||
|
// lock -> lock commit -> unlock -> unlock commit -> unlock
|
||||||
|
// The second unlock will throw an error due to there's no
|
||||||
|
// paired lock.
|
||||||
|
// 1.1 lock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 1.1");
|
||||||
|
param.is_try_lock_ = is_try_lock;
|
||||||
|
param.expired_time_ = expired_time;
|
||||||
|
ret = obj_lock_.lock(param,
|
||||||
|
store_ctx,
|
||||||
|
DEFAULT_OUT_TRANS_LOCK_OP,
|
||||||
|
lock_mode_in_same_trans,
|
||||||
|
allocator_,
|
||||||
|
conflict_tx_set);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
// 1.2 lock commit
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 1.2");
|
||||||
|
commit_version.set_base();
|
||||||
|
commit_scn.set_base();
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_LOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, commit_scn);
|
||||||
|
// 1.3 unlock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 1.3");
|
||||||
|
ret = obj_lock_.unlock(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
is_try_lock,
|
||||||
|
expired_time,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, commit_scn);
|
||||||
|
// 1.4 unlock commit
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 1.4");
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
// 1.5 try to unlock again
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 1.5");
|
||||||
|
ret = obj_lock_.unlock(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
is_try_lock,
|
||||||
|
expired_time,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_OBJ_LOCK_NOT_EXIST, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
|
||||||
|
// 2. UNLOCK AFTER A REPLAY AND COMMITTED UNLOCK
|
||||||
|
// The complete progress is:
|
||||||
|
// replay unlock -> unlock commit -> replay lock -> lock commit -> unlock
|
||||||
|
// The replay logic of unlocking doesn't check whether there's a paired lock,
|
||||||
|
// so the first unlcok will be executed successfully. And the second unlock
|
||||||
|
// will see the same unlock op which is before it, so it should return error.
|
||||||
|
// 2.1 replay unlock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 2.1");
|
||||||
|
ret = obj_lock_.recover_lock(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
// 2.2 unlock commit
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 2.2");
|
||||||
|
commit_version.set_base();
|
||||||
|
commit_scn.set_base();
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
// 2.3 replay lock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 2.3");
|
||||||
|
ret = obj_lock_.recover_lock(DEFAULT_OUT_TRANS_LOCK_OP,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
// 2.4 lock commit
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 2.4");
|
||||||
|
commit_version.set_base();
|
||||||
|
commit_scn.set_base();
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_LOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, commit_scn);
|
||||||
|
// 2.5 unlock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 2.5");
|
||||||
|
ret = obj_lock_.unlock(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
is_try_lock,
|
||||||
|
expired_time,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_OBJ_LOCK_NOT_EXIST, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, commit_scn);
|
||||||
|
// 2.6 unlock commit to compat
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 2.6");
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
// 2.7 check
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 2.7");
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_OBJ_LOCK_NOT_EXIST, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
|
||||||
|
// 3. UNLOCK AFTER A REPLAY AND UNCOMMITTED UNLOCK
|
||||||
|
// The complete progress is:
|
||||||
|
// replay unlock -> replay lock -> lock commit -> unlock -> unlcok commit
|
||||||
|
// The second unlock will be failed due to there's another unlock op
|
||||||
|
// in progress, but the latest unlock commit will executed successfully,
|
||||||
|
// because it can see and commit the first unlock.
|
||||||
|
// 3.1 replay unlock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 3.1");
|
||||||
|
ret = obj_lock_.recover_lock(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
// 3.2 replay lock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 3.2");
|
||||||
|
ret = obj_lock_.recover_lock(DEFAULT_OUT_TRANS_LOCK_OP,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
// 3.3 lock commit
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 3.3");
|
||||||
|
commit_version.set_base();
|
||||||
|
commit_scn.set_base();
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_LOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, commit_scn);
|
||||||
|
// 3.4 unlock
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 3.4");
|
||||||
|
ret = obj_lock_.unlock(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
is_try_lock,
|
||||||
|
expired_time,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_OBJ_UNLOCK_CONFLICT, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, commit_scn);
|
||||||
|
// 3.5 unlock commit
|
||||||
|
LOG_INFO("TestObjLock::out_trans_unlock_twice 3.5");
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_UNLOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_SUCCESS, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
// 3.6 check lock exist
|
||||||
|
LOG_INFO("TestObjLock::out_trans_lock 3.6");
|
||||||
|
ret = obj_lock_.update_lock_status(DEFAULT_OUT_TRANS_LOCK_OP,
|
||||||
|
commit_version,
|
||||||
|
commit_scn,
|
||||||
|
COMMIT_LOCK_OP_STATUS,
|
||||||
|
allocator_);
|
||||||
|
ASSERT_EQ(OB_OBJ_LOCK_NOT_EXIST, ret);
|
||||||
|
min_commited_scn = obj_lock_.get_min_ddl_lock_committed_scn(flushed_scn);
|
||||||
|
ASSERT_EQ(min_commited_scn, share::SCN::max_scn());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(TestObjLock, lock_conflict_in_in)
|
TEST_F(TestObjLock, lock_conflict_in_in)
|
||||||
{
|
{
|
||||||
// 1. IN TRANS VS IN TRANS
|
// 1. IN TRANS VS IN TRANS
|
||||||
|
@ -889,11 +889,11 @@ int ObOBJLock::check_allow_unlock_(
|
|||||||
const ObTableLockOp &unlock_op)
|
const ObTableLockOp &unlock_op)
|
||||||
{
|
{
|
||||||
int ret = OB_SUCCESS;
|
int ret = OB_SUCCESS;
|
||||||
// 1. only for OUT_TRANS unlock:
|
// only for OUT_TRANS unlock:
|
||||||
// 1) if the lock status is LOCK_OP_DOING, return OB_TRY_LOCK_ROW_CONFLICT
|
// 1) if the lock status is LOCK_OP_DOING, return OB_OBJ_LOCK_NOT_COMPLETED
|
||||||
// 2) if the lock status is LOCK_OP_COMPLETE, return OB_SUCCESS if
|
// for lock op, and return OB_OBJ_UNLOCK_CONFLICT for unlock op
|
||||||
// there is no unlock op, else if the unlock op is LOCK_OP_DOING return
|
// 2) if the lock status is LOCK_OP_COMPLETE, return OB_SUCCESS for locl op,
|
||||||
// OB_TRY_LOCK_ROW_CONFLICT
|
// but return OB_OBJ_LOCK_NOT_EXIST for unlock op to avoid unlocking repeatedly
|
||||||
// 3) if there is no lock, return OB_OBJ_LOCK_NOT_EXIST
|
// 3) if there is no lock, return OB_OBJ_LOCK_NOT_EXIST
|
||||||
int map_index = 0;
|
int map_index = 0;
|
||||||
ObTableLockOpList *op_list = NULL;
|
ObTableLockOpList *op_list = NULL;
|
||||||
@ -1266,18 +1266,31 @@ int ObOBJLock::check_op_allow_unlock_from_list_(
|
|||||||
DLIST_FOREACH(curr, *op_list) {
|
DLIST_FOREACH(curr, *op_list) {
|
||||||
if (curr->lock_op_.owner_id_ == lock_op.owner_id_) {
|
if (curr->lock_op_.owner_id_ == lock_op.owner_id_) {
|
||||||
lock_exist = true;
|
lock_exist = true;
|
||||||
if (curr->lock_op_.op_type_ == OUT_TRANS_LOCK) {
|
|
||||||
if (curr->lock_op_.lock_op_status_ == LOCK_OP_DOING) {
|
if (curr->lock_op_.lock_op_status_ == LOCK_OP_DOING) {
|
||||||
|
if (curr->lock_op_.op_type_ == OUT_TRANS_LOCK) {
|
||||||
ret = OB_OBJ_LOCK_NOT_COMPLETED;
|
ret = OB_OBJ_LOCK_NOT_COMPLETED;
|
||||||
} else if (curr->lock_op_.lock_op_status_ == LOCK_OP_COMPLETE) {
|
|
||||||
// continue to check for unlock op
|
|
||||||
} else {
|
|
||||||
ret = OB_ERR_UNEXPECTED;
|
|
||||||
}
|
|
||||||
} else if (curr->lock_op_.op_type_ == OUT_TRANS_UNLOCK) {
|
} else if (curr->lock_op_.op_type_ == OUT_TRANS_UNLOCK) {
|
||||||
ret = OB_OBJ_UNLOCK_CONFLICT;
|
ret = OB_OBJ_UNLOCK_CONFLICT;
|
||||||
} else {
|
} else {
|
||||||
ret = OB_ERR_UNEXPECTED;
|
ret = OB_ERR_UNEXPECTED;
|
||||||
|
LOG_ERROR("unexpected lock op type", K(ret), K(curr->lock_op_));
|
||||||
|
}
|
||||||
|
} else if (curr->lock_op_.lock_op_status_ == LOCK_OP_COMPLETE) {
|
||||||
|
// This status will occur in transaction disorder replaying,
|
||||||
|
// i.e. there's an unlcok op replay and commit before the
|
||||||
|
// lock op. So we return this error code to avoid continuing
|
||||||
|
// the unlocking operation.
|
||||||
|
if (curr->lock_op_.op_type_ == OUT_TRANS_UNLOCK) {
|
||||||
|
ret = OB_OBJ_LOCK_NOT_EXIST;
|
||||||
|
} else if (curr->lock_op_.op_type_ == OUT_TRANS_LOCK) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
ret = OB_ERR_UNEXPECTED;
|
||||||
|
LOG_ERROR("unexpected lock op type", K(ret), K(curr->lock_op_));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = OB_ERR_UNEXPECTED;
|
||||||
|
LOG_ERROR("unexpected lock op status", K(ret), K(curr->lock_op_));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // DLIST_FOREACH
|
} // DLIST_FOREACH
|
||||||
|
Loading…
x
Reference in New Issue
Block a user