From 5c6aae7bd356a54a74efdc7a87aab092a03e1eae Mon Sep 17 00:00:00 2001 From: idada Date: Fri, 5 Dec 2014 13:49:59 +0800 Subject: [PATCH 1/6] fix RWMutex bug --- deadlock.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/deadlock.go b/deadlock.go index 6ed69b2..907a85a 100644 --- a/deadlock.go +++ b/deadlock.go @@ -27,23 +27,27 @@ func (m *Mutex) Unlock() { } type RWMutex struct { - Mutex + monitor + sync.RWMutex } -func (rw *RWMutex) Lock() { - rw.Lock() +func (m *RWMutex) Lock() { + waitInfo := m.monitor.wait() + m.RWMutex.Lock() + m.monitor.using(waitInfo) } -func (rw *RWMutex) Unlock() { - rw.Unlock() +func (m *RWMutex) Unlock() { + m.monitor.release() + m.RWMutex.Unlock() } -func (rw *RWMutex) RLock() { - rw.Lock() +func (m *RWMutex) RLock() { + m.RWMutex.RLock() } -func (rw *RWMutex) RUnlock() { - rw.Unlock() +func (m *RWMutex) RUnlock() { + m.RWMutex.RUnlock() } var ( From 660b7fcee293f9219de82f934119a5195daa6833 Mon Sep 17 00:00:00 2001 From: idada Date: Fri, 5 Dec 2014 15:43:31 +0800 Subject: [PATCH 2/6] improve RWMutex support --- base_test.go | 49 ++++++++++++++++++- deadlock.go | 120 +++++++++++++++++++++++++++++++---------------- deadlock_test.go | 33 +++++++++++++ 3 files changed, 161 insertions(+), 41 deletions(-) diff --git a/base_test.go b/base_test.go index da2316a..6fc8580 100644 --- a/base_test.go +++ b/base_test.go @@ -22,7 +22,15 @@ func Benchmark_Lock2(b *testing.B) { } } -func Test_NoDeadlock(t *testing.T) { +func Benchmark_Lock3(b *testing.B) { + var mutex RWMutex + for i := 0; i < b.N; i++ { + mutex.Lock() + mutex.Unlock() + } +} + +func Test_NoDeadlock1(t *testing.T) { var ( mutex Mutex wait WaitGroup @@ -50,3 +58,42 @@ func Test_NoDeadlock(t *testing.T) { wait.Wait() } + +func Test_NoDeadlock2(t *testing.T) { + var ( + mutex RWMutex + wait WaitGroup + ) + + wait.Add(1) + go func() { + for i := 0; i < 10000; i++ { + mutex.Lock() + strconv.Itoa(i) + mutex.Unlock() + } + wait.Done() + }() + + wait.Add(1) + go func() { + for i := 0; i < 10000; i++ { + mutex.Lock() + strconv.Itoa(i) + mutex.Unlock() + } + wait.Done() + }() + + wait.Add(1) + go func() { + for i := 0; i < 10000; i++ { + mutex.RLock() + strconv.Itoa(i) + mutex.RUnlock() + } + wait.Done() + }() + + wait.Wait() +} diff --git a/deadlock.go b/deadlock.go index 907a85a..4efe08c 100644 --- a/deadlock.go +++ b/deadlock.go @@ -4,6 +4,7 @@ package sync import ( "bytes" + "container/list" "github.com/funny/debug" "github.com/funny/goid" "strconv" @@ -16,13 +17,13 @@ type Mutex struct { } func (m *Mutex) Lock() { - waitInfo := m.monitor.wait() + waitInfo := m.monitor.wait('w') m.Mutex.Lock() m.monitor.using(waitInfo) } func (m *Mutex) Unlock() { - m.monitor.release() + m.monitor.release('w') m.Mutex.Unlock() } @@ -32,21 +33,24 @@ type RWMutex struct { } func (m *RWMutex) Lock() { - waitInfo := m.monitor.wait() + waitInfo := m.monitor.wait('w') m.RWMutex.Lock() m.monitor.using(waitInfo) } func (m *RWMutex) Unlock() { - m.monitor.release() + m.monitor.release('w') m.RWMutex.Unlock() } func (m *RWMutex) RLock() { + waitInfo := m.monitor.wait('r') m.RWMutex.RLock() + m.monitor.using(waitInfo) } func (m *RWMutex) RUnlock() { + m.monitor.release('r') m.RWMutex.RUnlock() } @@ -57,56 +61,88 @@ var ( goStr = []byte("goroutine ") waitStr = []byte(" wait") holdStr = []byte(" hold") + readStr = []byte(" read") + writeStr = []byte(" write") lineStr = []byte{'\n'} ) -type monitor struct { - holder int32 - holderStack debug.StackInfo -} - type waiting struct { monitor *monitor + mode byte holder int32 holderStack debug.StackInfo } -func (m *monitor) wait() *waiting { +type monitor struct { + holders *list.List +} + +func (m *monitor) wait(mode byte) *waiting { globalMutex.Lock() defer globalMutex.Unlock() - waitInfo := &waiting{m, goid.Get(), debug.StackTrace(3, 0)} + waitInfo := &waiting{m, mode, goid.Get(), debug.StackTrace(3, 0)} waitingList[waitInfo.holder] = waitInfo - m.verify([]*waiting{waitInfo}) + if m.holders == nil { + m.holders = list.New() + } + + m.verify(mode, []*waiting{waitInfo}) return waitInfo } -func (m *monitor) verify(waitLink []*waiting) { - if m.holder != 0 { - // deadlock detected - if m.holder == waitLink[0].holder { - buf := new(bytes.Buffer) - buf.Write(titleStr) - for i := 0; i < len(waitLink); i++ { - buf.Write(goStr) - buf.WriteString(strconv.Itoa(int(waitLink[i].holder))) - buf.Write(waitStr) - buf.Write(lineStr) - buf.Write(waitLink[i].holderStack.Bytes(" ")) - - buf.Write(goStr) - buf.WriteString(strconv.Itoa(int(waitLink[i].monitor.holder))) - buf.Write(holdStr) - buf.Write(lineStr) - buf.Write(waitLink[i].monitor.holderStack.Bytes(" ")) +func (m *monitor) verify(mode byte, waitLink []*waiting) { + for i := m.holders.Front(); i != nil; i = i.Next() { + holder := i.Value.(*waiting) + if mode != 'r' || holder.mode != 'r' { + // deadlock detected + if holder.holder == waitLink[0].holder { + buf := new(bytes.Buffer) + buf.Write(titleStr) + for i := 0; i < len(waitLink); i++ { + buf.Write(goStr) + buf.WriteString(strconv.Itoa(int(waitLink[i].holder))) + buf.Write(waitStr) + if waitLink[i].mode == 'w' { + buf.Write(writeStr) + } else { + buf.Write(readStr) + } + buf.Write(lineStr) + buf.Write(waitLink[i].holderStack.Bytes(" ")) + + // lookup waiting for who + n := i + 1 + if n == len(waitLink) { + n = 0 + } + waitWho := waitLink[n] + + for j := waitLink[i].monitor.holders.Front(); j != nil; j = j.Next() { + waitHolder := j.Value.(*waiting) + if waitHolder.holder == waitWho.holder { + buf.Write(goStr) + buf.WriteString(strconv.Itoa(int(waitHolder.holder))) + buf.Write(holdStr) + if waitHolder.mode == 'w' { + buf.Write(writeStr) + } else { + buf.Write(readStr) + } + buf.Write(lineStr) + buf.Write(waitHolder.holderStack.Bytes(" ")) + break + } + } + } + panic(DeadlockError(buf.String())) + } + // the lock holder is waiting for another lock + if waitInfo, exists := waitingList[holder.holder]; exists { + waitInfo.monitor.verify(waitInfo.mode, append(waitLink, waitInfo)) } - panic(DeadlockError(buf.String())) - } - // the lock holder is waiting for another lock - if waitInfo, exists := waitingList[m.holder]; exists { - waitInfo.monitor.verify(append(waitLink, waitInfo)) } } } @@ -116,11 +152,15 @@ func (m *monitor) using(waitInfo *waiting) { defer globalMutex.Unlock() delete(waitingList, waitInfo.holder) - m.holder = waitInfo.holder - m.holderStack = waitInfo.holderStack + m.holders.PushBack(waitInfo) } -func (m *monitor) release() { - m.holder = 0 - m.holderStack = nil +func (m *monitor) release(mode byte) { + holder := goid.Get() + for i := m.holders.Back(); i != nil; i = i.Prev() { + if info := i.Value.(*waiting); info.holder == holder && info.mode == mode { + m.holders.Remove(i) + break + } + } } diff --git a/deadlock_test.go b/deadlock_test.go index 55b6c50..4b30c4d 100644 --- a/deadlock_test.go +++ b/deadlock_test.go @@ -106,3 +106,36 @@ func Test_DeadLock3(t *testing.T) { mutex3.Lock() }) } + +func Test_DeadLock4(t *testing.T) { + deadlockTest(t, func() { + var ( + mutex1 RWMutex + mutex2 RWMutex + mutex3 RWMutex + ) + + mutex1.Lock() + + var wait1 WaitGroup + wait1.Add(1) + go func() { + mutex2.RLock() + + var wait2 WaitGroup + wait2.Add(1) + go func() { + mutex3.Lock() + wait2.Done() + mutex2.Lock() + }() + wait2.Wait() + + wait1.Done() + mutex1.RLock() + }() + wait1.Wait() + + mutex3.Lock() + }) +} From 38d8d889b9e5cee1ac01e659ab66d66b9af1796d Mon Sep 17 00:00:00 2001 From: idada Date: Mon, 8 Dec 2014 20:50:17 +0800 Subject: [PATCH 3/6] fix bug --- deadlock.go | 120 +++++++++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/deadlock.go b/deadlock.go index 4efe08c..07fe60b 100644 --- a/deadlock.go +++ b/deadlock.go @@ -56,7 +56,7 @@ func (m *RWMutex) RUnlock() { var ( globalMutex = new(sync.Mutex) - waitingList = make(map[int32]*waiting) + waitingList = make(map[int32]*lockUsage) titleStr = []byte("[DEAD LOCK]\n") goStr = []byte("goroutine ") waitStr = []byte(" wait") @@ -66,101 +66,105 @@ var ( lineStr = []byte{'\n'} ) -type waiting struct { - monitor *monitor - mode byte - holder int32 - holderStack debug.StackInfo +type lockUsage struct { + monitor *monitor + mode byte + goid int32 + stack debug.StackInfo } type monitor struct { holders *list.List } -func (m *monitor) wait(mode byte) *waiting { +func (m *monitor) wait(mode byte) *lockUsage { globalMutex.Lock() defer globalMutex.Unlock() - waitInfo := &waiting{m, mode, goid.Get(), debug.StackTrace(3, 0)} - waitingList[waitInfo.holder] = waitInfo + waitInfo := &lockUsage{m, mode, goid.Get(), debug.StackTrace(3, 0)} + waitingList[waitInfo.goid] = waitInfo if m.holders == nil { m.holders = list.New() } - m.verify(mode, []*waiting{waitInfo}) + m.diagnose(mode, []*lockUsage{waitInfo}) return waitInfo } -func (m *monitor) verify(mode byte, waitLink []*waiting) { +func (m *monitor) diagnose(mode byte, waitLink []*lockUsage) { for i := m.holders.Front(); i != nil; i = i.Next() { - holder := i.Value.(*waiting) + holder := i.Value.(*lockUsage) if mode != 'r' || holder.mode != 'r' { // deadlock detected - if holder.holder == waitLink[0].holder { - buf := new(bytes.Buffer) - buf.Write(titleStr) - for i := 0; i < len(waitLink); i++ { - buf.Write(goStr) - buf.WriteString(strconv.Itoa(int(waitLink[i].holder))) - buf.Write(waitStr) - if waitLink[i].mode == 'w' { - buf.Write(writeStr) - } else { - buf.Write(readStr) - } - buf.Write(lineStr) - buf.Write(waitLink[i].holderStack.Bytes(" ")) - - // lookup waiting for who - n := i + 1 - if n == len(waitLink) { - n = 0 - } - waitWho := waitLink[n] - - for j := waitLink[i].monitor.holders.Front(); j != nil; j = j.Next() { - waitHolder := j.Value.(*waiting) - if waitHolder.holder == waitWho.holder { - buf.Write(goStr) - buf.WriteString(strconv.Itoa(int(waitHolder.holder))) - buf.Write(holdStr) - if waitHolder.mode == 'w' { - buf.Write(writeStr) - } else { - buf.Write(readStr) - } - buf.Write(lineStr) - buf.Write(waitHolder.holderStack.Bytes(" ")) - break - } - } - } - panic(DeadlockError(buf.String())) + if holder.goid == waitLink[0].goid { + deadlockPanic(waitLink) } // the lock holder is waiting for another lock - if waitInfo, exists := waitingList[holder.holder]; exists { - waitInfo.monitor.verify(waitInfo.mode, append(waitLink, waitInfo)) + if waitInfo, exists := waitingList[holder.goid]; exists { + waitInfo.monitor.diagnose(waitInfo.mode, append(waitLink, waitInfo)) } } } } -func (m *monitor) using(waitInfo *waiting) { +func (m *monitor) using(waitInfo *lockUsage) { globalMutex.Lock() defer globalMutex.Unlock() - delete(waitingList, waitInfo.holder) + delete(waitingList, waitInfo.goid) m.holders.PushBack(waitInfo) } func (m *monitor) release(mode byte) { - holder := goid.Get() + id := goid.Get() for i := m.holders.Back(); i != nil; i = i.Prev() { - if info := i.Value.(*waiting); info.holder == holder && info.mode == mode { + if info := i.Value.(*lockUsage); info.goid == id && info.mode == mode { m.holders.Remove(i) break } } } + +func deadlockPanic(waitLink []*lockUsage) { + buf := new(bytes.Buffer) + buf.Write(titleStr) + for i := 0; i < len(waitLink); i++ { + buf.Write(goStr) + buf.WriteString(strconv.Itoa(int(waitLink[i].goid))) + buf.Write(waitStr) + if waitLink[i].mode == 'w' { + buf.Write(writeStr) + } else { + buf.Write(readStr) + } + buf.Write(lineStr) + buf.Write(waitLink[i].stack.Bytes(" ")) + + // lookup waiting for who + n := i + 1 + if n == len(waitLink) { + n = 0 + } + waitWho := waitLink[n] + + for j := waitLink[i].monitor.holders.Front(); j != nil; j = j.Next() { + waitHolder := j.Value.(*lockUsage) + if waitHolder.goid == waitWho.goid { + buf.Write(goStr) + buf.WriteString(strconv.Itoa(int(waitHolder.goid))) + buf.Write(holdStr) + if waitHolder.mode == 'w' { + buf.Write(writeStr) + } else { + buf.Write(readStr) + } + buf.Write(lineStr) + buf.Write(waitHolder.stack.Bytes(" ")) + break + } + } + } + panic(DeadlockError(buf.String())) +} From 7028b9347c716a07eb38996a69cfba340628b04c Mon Sep 17 00:00:00 2001 From: idada Date: Wed, 24 Dec 2014 14:08:32 +0800 Subject: [PATCH 4/6] update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index f65c45c..473eed0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ +This project is dead since Go 1.4 (can't get goroutine id) +================================= + [English](http://github.com/funny/sync/blob/master/README_EN.md) --------- +自从Go 1.4发布之后,这个项目已经跪了 (无法获得goroutine id了) +================================ + [中文说明](http://github.com/funny/sync/blob/master/README_CN.md) --------- \ No newline at end of file From 5b23de27c1c2c7af9fb2128fd110404624c7a65a Mon Sep 17 00:00:00 2001 From: idada Date: Thu, 25 Dec 2014 10:22:32 +0800 Subject: [PATCH 5/6] update for Go 1.4 --- README.md | 6 ------ README_CN.md | 10 ++++++++++ README_EN.md | 10 ++++++++++ deadlock.go | 6 +++--- hack.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 9 deletions(-) create mode 100755 hack.sh diff --git a/README.md b/README.md index 473eed0..f65c45c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ -This project is dead since Go 1.4 (can't get goroutine id) -================================= - [English](http://github.com/funny/sync/blob/master/README_EN.md) --------- -自从Go 1.4发布之后,这个项目已经跪了 (无法获得goroutine id了) -================================ - [中文说明](http://github.com/funny/sync/blob/master/README_CN.md) --------- \ No newline at end of file diff --git a/README_CN.md b/README_CN.md index ba28933..1a00bd0 100644 --- a/README_CN.md +++ b/README_CN.md @@ -3,6 +3,16 @@ 这个包用来在开发调试期,帮助排查程序中的死锁情况。 +*** 请先运行运行时Hack脚本:[`hack.sh`](https://github.com/funny/sync/blob/master/)。 *** + +*** 从Go 1.4开始,我们无法在`runtime`包之外使用`goc`机制。 *** + +*** 所以现在我们需要把`GetGoId()`函数加入到`runtime`包。 *** + +*** 这个项目提供了一个Shell脚本用来修改和重新编译`runtime`包。*** + +*** 所以你需要一个包含代码的Go环境和Linux或Mac操作系统。 *** + 用法 ==== diff --git a/README_EN.md b/README_EN.md index 3cf8a1c..913214f 100644 --- a/README_EN.md +++ b/README_EN.md @@ -3,6 +3,16 @@ Introduction This package is used to detect deadlock in Go program. +*** Please run the hack script [`hack.sh`](https://github.com/funny/sync/blob/master/) before use. *** + +*** Since Go 1.4 we can't use `goc` file outside `runtime` package. *** + +*** So now we need to add the `GetGoId()` function into `runtime` package. *** + +*** This project provide a shell script to hack the `runtime` package and re-compile it. *** + +*** So you need a source-based Go environment and Linux/Mac system. *** + Usage ===== diff --git a/deadlock.go b/deadlock.go index 07fe60b..6bc78e7 100644 --- a/deadlock.go +++ b/deadlock.go @@ -6,7 +6,7 @@ import ( "bytes" "container/list" "github.com/funny/debug" - "github.com/funny/goid" + "runtime" "strconv" "sync" ) @@ -81,7 +81,7 @@ func (m *monitor) wait(mode byte) *lockUsage { globalMutex.Lock() defer globalMutex.Unlock() - waitInfo := &lockUsage{m, mode, goid.Get(), debug.StackTrace(3, 0)} + waitInfo := &lockUsage{m, mode, runtime.GetGoId(), debug.StackTrace(3, 0)} waitingList[waitInfo.goid] = waitInfo if m.holders == nil { @@ -118,7 +118,7 @@ func (m *monitor) using(waitInfo *lockUsage) { } func (m *monitor) release(mode byte) { - id := goid.Get() + id := runtime.GetGoId() for i := m.holders.Back(); i != nil; i = i.Prev() { if info := i.Value.(*lockUsage); info.goid == id && info.mode == mode { m.holders.Remove(i) diff --git a/hack.sh b/hack.sh new file mode 100755 index 0000000..4054b78 --- /dev/null +++ b/hack.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +echo "# Hack runtime" + +cat >> $GOROOT/src/runtime/runtime.c << EOF + +void +runtime·GetGoId(int32 ret) +{ + ret = g->goid; + USED(&ret); +} + +EOF + +cat >> $GOROOT/src/runtime/extern.go << EOF + +func GetGoId() int32 + +EOF + +cd $GOROOT/src +./make.bash + +cat > $$$$.go << EOF +package main + +import ( + "fmt" + "runtime" +) + +func main() { + runtime.GetGoId() + fmt.Print("done") +} +EOF +x=`go run $$$$.go` +rm $$$$.go + +echo "" +echo "" +echo "---" + +if [ $x = "done" ]; then + echo "Done" +else + echo "Failed" +fi From 7d74f6c02bd3999f90fa1ffd944e409eded5b620 Mon Sep 17 00:00:00 2001 From: idada Date: Fri, 5 Jun 2015 11:33:06 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E6=94=B9=E4=B8=BA=E8=BE=83=E6=85=A2?= =?UTF-8?q?=E4=BD=86=E4=B8=8D=E9=9C=80=E8=A6=81hack=E7=9A=84=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E8=8E=B7=E5=8F=96Goroutine=20ID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 77 ++++++++++++++++++++++++++++++++++++++++++++--- README_CN.md | 84 ---------------------------------------------------- README_EN.md | 83 --------------------------------------------------- deadlock.go | 14 ++++----- hack.sh | 49 ------------------------------ 5 files changed, 79 insertions(+), 228 deletions(-) delete mode 100644 README_CN.md delete mode 100644 README_EN.md delete mode 100755 hack.sh diff --git a/README.md b/README.md index f65c45c..ba28933 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,74 @@ -[English](http://github.com/funny/sync/blob/master/README_EN.md) ---------- +介绍 +==== -[中文说明](http://github.com/funny/sync/blob/master/README_CN.md) ---------- \ No newline at end of file +这个包用来在开发调试期,帮助排查程序中的死锁情况。 + +用法 +==== + +通常我们项目中引用到原生`sync`包的代码会像这样: + +```go +package myapp + +import "sync" + +var MyLock sync.Mutex + +func MyFunc() { + MyLock.Lock() + defer MyLock.Unlock() + + // ....... +} +``` + +只需要将原来引用`sync`的代码改为引用`github.com/funny/sync`包,不需要修改别的代码: + + +```go +package myapp + +import "github.com/funny/sync" + +var MyLock sync.Mutex + +func MyFunc() { + MyLock.Lock() + defer MyLock.Unlock() + + // ....... +} +``` + +这时候死锁诊断还没有被启用,因为做了条件编译,所以锁的开销跟原生`sync`包是一样的。 + +当需要编译一个带死锁诊断的版本的时候,在`go build --tags`列表中加入`deadlock`标签。 + +例如这样: + +``` +go build -tags deadlock myproject +``` + +同样这个标签也用于单元测试,否则默认的单元测试会死锁: + +``` +go test -tags deadlock -v +``` + + +原理 +==== + +在开启死锁检查的时候,系统会维护一份全局的锁等待列表,其次每个锁都会有当前使用者的信息。 + +当一个goroutine要等待一个锁的时候,系统会到全局的等待列表里面查找当前这个锁的使用者,是否间接或直接的正在等待当前请求锁的这个goroutine。 + +死锁不一定只发生在两个goroutine之间,极端情况也可能是一个链条状的依赖关系,又或者可能出现自身重复加锁的死锁情况。 + +当出现死锁的时候,系统将提取死锁链上的所有goroutine的堆栈跟踪信息,方便排查故障原因。 + +因为需要维护一份全局的锁等待列表,所以这里会出现额外并且集中的一个全局锁开销,会导致明显的程序的并发性能下降。 + +全局锁的问题还会再继续研究和加以改进,但是目前这个包是不能用于生产环境的,只能用在开发和调试期作为死锁诊断的辅助工具。 diff --git a/README_CN.md b/README_CN.md deleted file mode 100644 index 1a00bd0..0000000 --- a/README_CN.md +++ /dev/null @@ -1,84 +0,0 @@ -介绍 -==== - -这个包用来在开发调试期,帮助排查程序中的死锁情况。 - -*** 请先运行运行时Hack脚本:[`hack.sh`](https://github.com/funny/sync/blob/master/)。 *** - -*** 从Go 1.4开始,我们无法在`runtime`包之外使用`goc`机制。 *** - -*** 所以现在我们需要把`GetGoId()`函数加入到`runtime`包。 *** - -*** 这个项目提供了一个Shell脚本用来修改和重新编译`runtime`包。*** - -*** 所以你需要一个包含代码的Go环境和Linux或Mac操作系统。 *** - -用法 -==== - -通常我们项目中引用到原生`sync`包的代码会像这样: - -```go -package myapp - -import "sync" - -var MyLock sync.Mutex - -func MyFunc() { - MyLock.Lock() - defer MyLock.Unlock() - - // ....... -} -``` - -只需要将原来引用`sync`的代码改为引用`github.com/funny/sync`包,不需要修改别的代码: - - -```go -package myapp - -import "github.com/funny/sync" - -var MyLock sync.Mutex - -func MyFunc() { - MyLock.Lock() - defer MyLock.Unlock() - - // ....... -} -``` - -这时候死锁诊断还没有被启用,因为做了条件编译,所以锁的开销跟原生`sync`包是一样的。 - -当需要编译一个带死锁诊断的版本的时候,在`go build --tags`列表中加入`deadlock`标签。 - -例如这样: - -``` -go build -tags deadlock myproject -``` - -同样这个标签也用于单元测试,否则默认的单元测试会死锁: - -``` -go test -tags deadlock -v -``` - - -原理 -==== - -在开启死锁检查的时候,系统会维护一份全局的锁等待列表,其次每个锁都会有当前使用者的信息。 - -当一个goroutine要等待一个锁的时候,系统会到全局的等待列表里面查找当前这个锁的使用者,是否间接或直接的正在等待当前请求锁的这个goroutine。 - -死锁不一定只发生在两个goroutine之间,极端情况也可能是一个链条状的依赖关系,又或者可能出现自身重复加锁的死锁情况。 - -当出现死锁的时候,系统将提取死锁链上的所有goroutine的堆栈跟踪信息,方便排查故障原因。 - -因为需要维护一份全局的锁等待列表,所以这里会出现额外并且集中的一个全局锁开销,会导致明显的程序的并发性能下降。 - -全局锁的问题还会再继续研究和加以改进,但是目前这个包是不能用于生产环境的,只能用在开发和调试期作为死锁诊断的辅助工具。 diff --git a/README_EN.md b/README_EN.md deleted file mode 100644 index 913214f..0000000 --- a/README_EN.md +++ /dev/null @@ -1,83 +0,0 @@ -Introduction -============ - -This package is used to detect deadlock in Go program. - -*** Please run the hack script [`hack.sh`](https://github.com/funny/sync/blob/master/) before use. *** - -*** Since Go 1.4 we can't use `goc` file outside `runtime` package. *** - -*** So now we need to add the `GetGoId()` function into `runtime` package. *** - -*** This project provide a shell script to hack the `runtime` package and re-compile it. *** - -*** So you need a source-based Go environment and Linux/Mac system. *** - -Usage -===== - -Normally, we import default `sync` package in our project like this: - -```go -package myapp - -import "sync" - -var MyLock sync.Mutex - -func MyFunc() { - MyLock.Lock() - defer MyLock.Unlock() - - // ....... -} -``` - -Just replace the default `sync` to `github.com/funny/sync`, no need to change others: - - -```go -package myapp - -import "github.com/funny/sync" - -var MyLock sync.Mutex - -func MyFunc() { - MyLock.Lock() - defer MyLock.Unlock() - - // ....... -} -``` - -Currently, deadlock detection not yet enabled, the performance of `Mutext` and `RWMutex` just like default. - -When you need to compile a deadlock detection enabled version. Just add `deadlock` tag into `go build --tags` command. - -For example: - -``` -go build -tags deadlock myproject -``` - -This tag used for the unit test too. Otherwise the default unit test will deadlock: - -``` -go test -tags deadlock -v -``` - -How it works -============ - -When deadlock detection enabled, system will maintain a global lock waiting list, and each `Mutex` and `RWMutex` will keep owner goroutine's information. - -When a goroutine will waiting a lock, system will lookup the lock owner goroutine is whether waiting for the requester goroutine in directly or indirectly. - -Deadlock not only happens between two goroutines, sometimes the deadlock is a link, and deadlock happens when a goroutine repeat lock a `Mutext` too. - -When deadlock happens, system will dump stack trace of the gorotuines in the deadlock link. - -Because we need a global lock waiting list, so the deadlock detection will drop the performance. - -So, please don't use deadlock detection in production environment. \ No newline at end of file diff --git a/deadlock.go b/deadlock.go index 6bc78e7..3394081 100644 --- a/deadlock.go +++ b/deadlock.go @@ -6,8 +6,6 @@ import ( "bytes" "container/list" "github.com/funny/debug" - "runtime" - "strconv" "sync" ) @@ -56,7 +54,7 @@ func (m *RWMutex) RUnlock() { var ( globalMutex = new(sync.Mutex) - waitingList = make(map[int32]*lockUsage) + waitingList = make(map[string]*lockUsage) titleStr = []byte("[DEAD LOCK]\n") goStr = []byte("goroutine ") waitStr = []byte(" wait") @@ -69,7 +67,7 @@ var ( type lockUsage struct { monitor *monitor mode byte - goid int32 + goid string stack debug.StackInfo } @@ -81,7 +79,7 @@ func (m *monitor) wait(mode byte) *lockUsage { globalMutex.Lock() defer globalMutex.Unlock() - waitInfo := &lockUsage{m, mode, runtime.GetGoId(), debug.StackTrace(3, 0)} + waitInfo := &lockUsage{m, mode, debug.GoroutineID(), debug.StackTrace(3)} waitingList[waitInfo.goid] = waitInfo if m.holders == nil { @@ -118,7 +116,7 @@ func (m *monitor) using(waitInfo *lockUsage) { } func (m *monitor) release(mode byte) { - id := runtime.GetGoId() + id := debug.GoroutineID() for i := m.holders.Back(); i != nil; i = i.Prev() { if info := i.Value.(*lockUsage); info.goid == id && info.mode == mode { m.holders.Remove(i) @@ -132,7 +130,7 @@ func deadlockPanic(waitLink []*lockUsage) { buf.Write(titleStr) for i := 0; i < len(waitLink); i++ { buf.Write(goStr) - buf.WriteString(strconv.Itoa(int(waitLink[i].goid))) + buf.WriteString(waitLink[i].goid) buf.Write(waitStr) if waitLink[i].mode == 'w' { buf.Write(writeStr) @@ -153,7 +151,7 @@ func deadlockPanic(waitLink []*lockUsage) { waitHolder := j.Value.(*lockUsage) if waitHolder.goid == waitWho.goid { buf.Write(goStr) - buf.WriteString(strconv.Itoa(int(waitHolder.goid))) + buf.WriteString(waitHolder.goid) buf.Write(holdStr) if waitHolder.mode == 'w' { buf.Write(writeStr) diff --git a/hack.sh b/hack.sh deleted file mode 100755 index 4054b78..0000000 --- a/hack.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/sh - -echo "# Hack runtime" - -cat >> $GOROOT/src/runtime/runtime.c << EOF - -void -runtime·GetGoId(int32 ret) -{ - ret = g->goid; - USED(&ret); -} - -EOF - -cat >> $GOROOT/src/runtime/extern.go << EOF - -func GetGoId() int32 - -EOF - -cd $GOROOT/src -./make.bash - -cat > $$$$.go << EOF -package main - -import ( - "fmt" - "runtime" -) - -func main() { - runtime.GetGoId() - fmt.Print("done") -} -EOF -x=`go run $$$$.go` -rm $$$$.go - -echo "" -echo "" -echo "---" - -if [ $x = "done" ]; then - echo "Done" -else - echo "Failed" -fi