[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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
// 1. IN TRANS VS IN TRANS
|
||||
|
@ -889,11 +889,11 @@ int ObOBJLock::check_allow_unlock_(
|
||||
const ObTableLockOp &unlock_op)
|
||||
{
|
||||
int ret = OB_SUCCESS;
|
||||
// 1. only for OUT_TRANS unlock:
|
||||
// 1) if the lock status is LOCK_OP_DOING, return OB_TRY_LOCK_ROW_CONFLICT
|
||||
// 2) if the lock status is LOCK_OP_COMPLETE, return OB_SUCCESS if
|
||||
// there is no unlock op, else if the unlock op is LOCK_OP_DOING return
|
||||
// OB_TRY_LOCK_ROW_CONFLICT
|
||||
// only for OUT_TRANS unlock:
|
||||
// 1) if the lock status is LOCK_OP_DOING, return OB_OBJ_LOCK_NOT_COMPLETED
|
||||
// for lock op, and return OB_OBJ_UNLOCK_CONFLICT for unlock op
|
||||
// 2) if the lock status is LOCK_OP_COMPLETE, return OB_SUCCESS for locl op,
|
||||
// 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
|
||||
int map_index = 0;
|
||||
ObTableLockOpList *op_list = NULL;
|
||||
@ -1266,18 +1266,31 @@ int ObOBJLock::check_op_allow_unlock_from_list_(
|
||||
DLIST_FOREACH(curr, *op_list) {
|
||||
if (curr->lock_op_.owner_id_ == lock_op.owner_id_) {
|
||||
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;
|
||||
} else if (curr->lock_op_.lock_op_status_ == LOCK_OP_COMPLETE) {
|
||||
// continue to check for unlock op
|
||||
} else if (curr->lock_op_.op_type_ == OUT_TRANS_UNLOCK) {
|
||||
ret = OB_OBJ_UNLOCK_CONFLICT;
|
||||
} else {
|
||||
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 if (curr->lock_op_.op_type_ == OUT_TRANS_UNLOCK) {
|
||||
ret = OB_OBJ_UNLOCK_CONFLICT;
|
||||
} else {
|
||||
ret = OB_ERR_UNEXPECTED;
|
||||
LOG_ERROR("unexpected lock op status", K(ret), K(curr->lock_op_));
|
||||
}
|
||||
}
|
||||
} // DLIST_FOREACH
|
||||
|
Loading…
x
Reference in New Issue
Block a user