OpenGauss线程管理-轻量锁监控线程-LWlockmonitor
轻量级锁(LWLock)主要提供对共享内存的互斥访问,比如Clog buffer(事务提交状态缓存)、Shared buffers(数据页缓存)、Substran buffer(子事务缓存)等。该轻量级锁监控线程主要检测轻量级锁(LWLock)产生的死锁。
术语解释
- 无锁:指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
- 偏向锁:在开启偏向锁机制的情况下,某个线程获得锁,当该线程下次再想要获得锁时,不需要再获得锁,直接就可以执行同步代码。
- 轻量级锁(LWLock):当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。在没有多线程竞争的情况下,使用轻量级锁能够减少性能消耗,但是当多个线程同时竞争锁时,轻量级锁会膨胀为重量级锁。
- 重量级锁:即当有其他线程占用锁时,当前线程会进入阻塞状态。
- 自旋锁:在自旋状态下,当一个线程A尝试进入同步代码块,但是当前的锁已经被线程B占有时,线程A不进入阻塞状态,而是不停的空转,等待线程B释放锁。
LWlockmonitor线程
路径:openGauss-server/src/gausskernel/process/postmaster/lwlockmonitor.cpp
该线程将检测到lwlock死锁的发生,并进行后期处理。
在主线程中的使用
在GaussDbThreadMain中通过thread_role进入LWlockmonitor线程
代码理解
FaultMonitorMain
NON_EXEC_STATIC void FaultMonitorMain()
{
sigjmp_buf localSigjmpBuf;
MemoryContext lwm_context = NULL;
lwm_light_detect* prev_snapshot = NULL;
lwm_light_detect* curr_snapshot = NULL;
long cur_timeout = 0;
/* 我们现在是postmaster子流程 */
IsUnderPostmaster = true;
t_thrd.proc_cxt.MyProcPid = gs_thread_self();
ereport(DEBUG5, (errmsg("lwlockmonitor process is started: %lu", t_thrd.proc_cxt.MyProcPid)));
(void)gspqsignal(SIGHUP, LWLockMonitorSigHupHandler); /* 设置标志以读取配置文件 */
(void)gspqsignal(SIGINT, LWLockMonitorShutdownHandler); /* 请求关闭 */
(void)gspqsignal(SIGTERM, LWLockMonitorShutdownHandler); /* 请求关闭 */
(void)gspqsignal(SIGQUIT, SIG_IGN);
(void)gspqsignal(SIGALRM, SIG_IGN);
(void)gspqsignal(SIGPIPE, SIG_IGN);
(void)gspqsignal(SIGUSR1, SIG_IGN);
(void)gspqsignal(SIGUSR2, SIG_IGN); /* not used */
/* 重置一些postmaster接受但不在此接受的信号 */
(void)gspqsignal(SIGCHLD, SIG_DFL);
(void)gspqsignal(SIGTTIN, SIG_DFL);
(void)gspqsignal(SIGTTOU, SIG_DFL);
(void)gspqsignal(SIGCONT, SIG_DFL);
(void)gspqsignal(SIGWINCH, SIG_DFL);
/* 我们始终允许SIGQUIT(快速模具) */
(void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);
/*
* 创建一个我们将在其中完成所有工作的内存上下文。
* 我们这样做是为了在错误恢复期间重置上下文,从而避免可能的内存泄漏。
* 以前,此代码只在t_thrd中运行。topmemcxt,但重置它将是一个非常糟糕的主意。
*/
lwm_context = AllocSetContextCreate(t_thrd.top_mem_cxt,
"LWLock Monitor",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
(void)MemoryContextSwitchTo(lwm_context);
#ifdef ENABLE_UT
/* 单元测试用例 */
ut_test_find_deadlock_cycle();
#endif /* ENABLE_UT */
int curTryCounter;
int* oldTryCounter = NULL;
if (sigsetjmp(localSigjmpBuf, 1) != 0) {
gstrace_tryblock_exit(true, oldTryCounter);
/* 由于未使用PG_TRY,因此必须手动重置错误堆栈 */
t_thrd.log_cxt.error_context_stack = NULL;
t_thrd.log_cxt.call_stack = NULL;
/* 清理时防止中断 */
HOLD_INTERRUPTS();
/* 向服务器日志报告错误 */
EmitErrorReport();
/* lsc持有的释放资源 */
AtEOXact_SysDBCache(false);
/*
* 现在返回正常的顶级上下文,并清除ErrorContext以备下次使用。
*/
(void)MemoryContextSwitchTo(lwm_context);
FlushErrorState();
/* 刷新顶级上下文中的所有泄漏数据 */
MemoryContextResetAndDeleteChildren(lwm_context);
/* 现在我们可以再次允许中断 */
RESUME_INTERRUPTS();
/*
* 出现任何错误后至少休眠1秒。写入错误可能会重复出现,我们不希望尽快填写错误日志。
*/
pg_usleep(1000000L);
}
oldTryCounter = gstrace_tryblock_entry(&curTryCounter);
/* 我们现在可以处理安装(错误) */
t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf;
/* 解除封锁信号(当邮政局长分叉我们时,信号被封锁) */
gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);
(void)gs_signal_unblock_sigusr2();
pgstat_report_appname("LWLock Monitor");
pgstat_report_activity(STATE_IDLE, NULL);
/* 设置当前监视器超时 */
cur_timeout = (long)u_sess->attr.attr_common.fault_mon_timeout * 60 * 1000;
prev_snapshot = NULL;
curr_snapshot = NULL;
for (;;) {
int rc = 0;
/* 清除所有已挂起的唤醒 */
ResetLatch(&t_thrd.proc->procLatch);
/* 处理最近收到的任何请求或信号。 */
if (t_thrd.lwm_cxt.got_SIGHUP) {
t_thrd.lwm_cxt.got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
long newTimeout = (long)u_sess->attr.attr_common.fault_mon_timeout * 60 * 1000;
if (newTimeout != cur_timeout) {
/* 用于lwlock调试信息 */
DumpLWLockInfoToServerLog();
}
/* 更新监视器超时 */
cur_timeout = newTimeout;
}
if (t_thrd.lwm_cxt.shutdown_requested) {
/* lwlockmonitor的正常出口在这里 */
proc_exit(0);
}
/* 如果设置u_sess->attr.attrcommon,则禁用此功能。fault_mon_timeout为0 */
if (u_sess->attr.attr_common.fault_mon_timeout > 0) {
/* 开始做主要工作 */
if (NULL != prev_snapshot) {
lwm_deadlock deadlock = {NULL, 0, 0, 0};
bool continue_next = false;
/* 第1阶段:使用快速变化计数进行轻量化检测 */
curr_snapshot = pgstat_read_light_detect();
continue_next = lwm_compare_light_detect(prev_snapshot, curr_snapshot);
if (continue_next) {
/* 第2阶段(如果需要):lwlock死锁的重量级诊断 */
int candidates_num = 0;
int* candidates_pos = lwm_find_candidates(prev_snapshot, curr_snapshot, &candidates_num);
lwm_lwlocks* backend_locks =
pgstat_read_diagnosis_data(curr_snapshot, candidates_pos, candidates_num);
pfree_ext(candidates_pos);
continue_next = lwm_heavy_diagnosis(&deadlock, backend_locks, candidates_num);
/* 清理*/
for (int i = 0; i < candidates_num; i++) {
lwm_lwlocks* lwlock = backend_locks + i;
pfree_ext(lwlock->held_lwlocks);
}
pfree_ext(backend_locks);
}
if (continue_next) {
/* 阶段3(如果需要):lwlock死锁的自动修复 */
lw_deadlock_auto_healing(&deadlock);
}
/* 准备下一个监视器,并保留当前快照 */
if (NULL != deadlock.info) {
pfree_ext(deadlock.info);
}
pfree_ext(prev_snapshot);
prev_snapshot = curr_snapshot;
curr_snapshot = NULL;
} else {
/* 第一次获取快照 */
prev_snapshot = pgstat_read_light_detect();
curr_snapshot = NULL;
}
} else {
/* 只需设置默认超时:10分钟 */
cur_timeout = 10 * 60 * 1000;
}
pgstat_report_activity(STATE_IDLE, NULL);
rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, cur_timeout);
/*
* 如果postmaster去世,紧急救援。这是为了避免手动清理所有邮政局长的孩子。
*/
if (((unsigned int)rc) & WL_POSTMASTER_DEATH) {
gs_thread_exit(1);
}
}
}
其他函数
函数 | 功能 |
---|---|
bool lwm_compare_light_detect(lwm_light_detect* olds, lwm_light_detect* news) | 比较两个光检测数据,检查是否有任何线程可能被其他线程或其自身阻塞。可能会发生死锁吗? |
int* lwm_find_candidates(lwm_light_detect* olds, lwm_light_detect* news, int* out_num) | 找到所有候选人,记住他们的职位和号码 |
static inline void init_entry_in_the_first_insert(lock_entry* entry, LWLockAddr *entry_key) | 如果是第一次插入则初始化条目 |
static inline void map_from_lock_to_holder(HTAB* map, lwlock_id_mode* held_lwlocks, int n, lock_entry_id* holder) | 记住这把锁的持有人。现在持有者持有编号为n的锁 |
static void build_holder_and_waiter_map(HTAB* map, lwm_lwlocks* candidates, int num_candidates) | 为这两个关系构建哈希映射(1)lwlock id及其服务员;(2)lwlock id及其持有人; |
static void destroy_lock_hashtbl(HTAB* lock_map) | 销毁锁哈希表 |
static void build_map_from_threadid_to_lockid(HTAB* map, lwm_lwlocks* candidates, int num_candidates) | 在线程id与其获取的lwlock之间建立映射 |
static bool find_lock_cycle_recurse(thread_entry* check_thread, HTAB* lock_map, HTAB* tid_map, lwm_visited_thread* visited, lwm_deadlock* deadlock, int depth) | 查找锁定周期的安全版本 |
static bool find_cycle_start_point(lwm_deadlock* deadlock) | 找到这个死锁循环的起点 |
static bool find_lock_cycle( lwm_lwlocks* lock, HTAB* lock_map, HTAB* tid_map, lwm_visited_thread* visited, lwm_deadlock* deadlock) | 输入点以查找死锁循环 |
void lwm_deadlock_report(lwm_deadlock* deadlock) | Report a detected deadlock, with available details. |
bool lwm_heavy_diagnosis(lwm_deadlock* deadlock, lwm_lwlocks* candidates, int num_candidates) | lwlock死锁检查并在需要时报告 |
static int choose_one_victim(lwm_deadlock* deadlock, int* out_idx) | 为这个死锁循环选择一个牺牲品 |