spin lock
使用 cas 去获取锁,先获取 spins_per_delay 次数,如果还失败,则每次获取失败将 delay 时长延长至 1~2倍 delay 值加 0.5 us,spins_per_delay 的值在获取锁后会做更新,如果这次没有等待,则下次可以多尝试 100 次(最多不超过1000次),如果这次第一次尝试是失败的,则下次尝试少一次,(最少 10 次)
fast path 加锁失败路径
首先将 fast path 锁转移至 hash 表
锁的链接
lock 指锁实例。
lock.procLocks 指获取锁的进程对应这个锁的 procLock 链表,可以通过 (PROCLOCK *)(lock.procLocks.next - 32) 来还原。
lock.waitProcs 指等待这一锁的进程结构的链表。可以 通过 (PGPROC *)lock.waitProcs.links.next 来还原
注:链接为空的条件不是next 和 prev 为空,而是 next 和 prev 都指向链表自己(lock.procLocks.next == &lock.procLocks)
regular 锁授予
每当请求一个 lock 的一个 mode 时,lock->requested[mode] 就会加1,当成功授予一个锁时,lock 上的 lock->granted[lockmode] 计数会加一(如果 grant[mode] 数与 request[mode] 数一样时,还会把 waitmask[mode] 置为 false),且 grantMask[mode] 置为 true
锁请求与等待进程的冲突检查
锁冲突矩阵:
(引用)
锁请求与等待进程的冲突检查:
lock->waitMask 表示等待锁的人等的 lock mode,如果自己即将获取的锁与其它等待进程的 lock mode 冲突(比如我要拿 shared 锁,但有人拿了 exclusive 锁),意味着我拿这个锁后会有人等我,即有冲突。
锁请求与拿锁进程的冲突检查:
如果我要拿的锁与已经拿锁人的 mode 不冲突(比如有人拿了share update,有人在等 share,而我要拿的是 row share,与两者都不冲突),则检查无冲突
如果我要拿的锁与我自己的锁冲突,或与我自己所在组拿的锁冲突,则不算冲突,检查如下:
- 将我的 proclock→holdmask 中的mode 从 lock→granted[mode]中减1,看是否还有 lock→granted 与我将要拿的mode 冲突
- 将已经拿锁的 lock->procLocks 中属于我的锁组的找出来,将这部分从 lock→granted[mode] 中减去1后,看是否还有 lock→granted 与 我要的 mode 冲突
检查到冲突时等待队列位置判断:
如果有冲突,则要等锁 WaitOnLock→ProcSleep,这里需要判断自己加到等待队列的什么位置,这里的判断需要找到冲突的锁组,将自己加在冲突锁组的前面
- 找出所有我在锁组获取的 lock mode
- 从前往后找出其它锁组的等待进程,检查它们是否等待我在的锁组(它们等的锁 mode 与我的锁组拿的锁 mode 冲突)
- 如果它们在等我的锁组,我也在等它们的锁组(它们拿的锁 mode 与我等的锁 mode 冲突),则发现死锁,这种情况不用等,直接 error
- 如果它们不需要等我(我与它无冲突),则我排在它后面
- 如果找到了等我的进程,而我又不需要等它们,则我要的等待要排在它前面
- 这里代码里有一个特殊情况(我感觉,事实上这种情况应该永远不存在,因为前面已经过滤掉了)如果排在前面的人,它们等我的锁与我要等的锁不冲突,意味它们虽然等我的锁组,但等的不是我,而是等我的锁组其它成员(比如前面都等 shared row exclusive 锁,我要等的是 share 锁与前面等待的锁不冲突,但与我们组拿的row exclusive 锁冲突,则我也可以直接拿这个 share 锁),这种情况要再判断一次是否是与我自己所在组拿的锁冲突,如果是这种情况,不用 sleep,直接获取。
以后每次唤醒,我需要去检查一次死锁,如果是被 auto vacuum 卡了,则允许我 kill 它一次。