openGauss在获取锁时如果没有冲突可以直接上锁;如果有冲突则设置一个定时器timer,并进入等待,过一段时间会被timer唤起进行死锁检测。如果在某个锁的等锁队列中,进程T2排在进程T1后面,且进程T2需要获取的锁与T1需要获取的锁资源冲突,则T2到T1会有一条软等待边(soft edge)。如果进程T2的加锁请求与T1进程所持有的锁冲突,则有一条硬等待边(hard edge)。那么整体思路就是通过递归调用,从当前顶点等锁的顶点出发,沿着等待边向前走,看是否存在环,如果环中有soft edge,说明环中两个进程都在等锁,重新排序,尝试解决死锁冲突。如果没有soft edge,那么只能终止当前等锁的事务,解决死锁等待环。如图5-19所示,虚线代表soft edge,实线代表hard fdge。线程A等待线程B,线程B等待线程C,线程C等待线程A,因为线程A等待线程B的是soft edge,进行一次调整成为图5-19右边的等待关系,此时发现线程A等待线程C,线程C等待线程A,没有soft edge,检测到死锁。

图5-19 常规锁死锁检测示意图
主要函数如下。
(1) DeadLockCheck:死锁检测函数。
(2) DeadLockCheckRecurse:如果死锁则返回true,如果有soft edge,返回false并且尝试解决死锁冲突。
(3) check_stack_depth:openGauss会检查死锁递归检测堆栈(死锁检测递归栈过长,会引发在死锁检测时,长期持有所有锁的LWLock分区,从而阻塞业务)。
(4) CheckDeadLockRunningTooLong:openGauss会检查死锁检测时间,防止死锁检测时间过长,阻塞后面所有业务。对应的代码如下:
static void CheckDeadLockRunningTooLong(int depth)
{ /* 每4层检测一下 */
if (depth > 0 && ((depth % 4) == 0)) {
TimestampTz now = GetCurrentTimestamp();
long secs = 0;
int usecs = 0;
if (now > t_thrd.storage_cxt.deadlock_checker_start_time) {
TimestampDifference(t_thrd.storage_cxt.deadlock_checker_start_time, now, &secs, &usecs);
if (secs > 600) { /* 如果从死锁检测开始超过十分钟,则报错处理。 */
#ifdef USE_ASSERT_CHECKING
DumpAllLocks();/* 在debug版本时,导出所有的锁信息,便于定位问题。 */
#endif
ereport(defence_errlevel(), (errcode(ERRCODE_INTERNAL_ERROR),
errmsg("Deadlock checker runs too long and is greater than 10 minutes.")));
}
}
}
}
(5) FindLockCycle:检查是否有死锁环。
(6) FindLockCycleRecurse:死锁检测内部递归调用函数。
相应的数据结构有:
(1) 死锁检测中最核心最关键的有向边数据结构。对应的代码如下:
typedef struct EDGE {
PGPROC *waiter; /* 等待的线程 */
PGPROC *blocker; /* 阻塞的线程 */
int pred; /* 拓扑排序的工作区 */
int link; /* 拓扑排序的工作区 */
} EDGE;
(2) 可重排的一个等待队列。对应的代码如下:
typedef struct WAIT_ORDER {
LOCK *lock; /* the lock whose wait queue is described */
PGPROC **procs; /* array of PGPROC *'s in new wait order */
int nProcs;
} WAIT_ORDER;
(3) 死锁检测最后打印的相应信息。对应的代码如下:
typedef struct DEADLOCK_INFO {
LOCKTAG locktag; /* 等待锁对象的唯一标识 */
LOCKMODE lockmode; /* 等待锁对象的锁类型 */
ThreadId pid; /* 阻塞线程的线程ID */
} DEADLOCK_INFO;




