如何以编程方式证明此代码具有竞争条件? [英] How to programatically prove this code has a race condition?
问题描述
我被告知此代码在设计上具有竞争条件,尽管我尝试了一下,但我无法证明它确实存在.
I'm told this code has a race condition by design, though try as I may, I'm unable to prove it does.
func (h *handler) loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h.Log = log.WithFields(log.Fields{
"method": r.Method,
"requestURI": r.RequestURI,
})
next.ServeHTTP(w, r)
})
}
我尝试了go build -race
,然后运行二进制文件:PORT=3000 ./main
&加载hey -n 10000 -c 200 http://localhost:3000
之类的创建者.
I tried go build -race
, then running the binary: PORT=3000 ./main
& load creators like hey -n 10000 -c 200 http://localhost:3000
.
此处的其余代码: https://raw .githubusercontent.com/kaihendry/context-youtube/master/5/main.go
或
type handler struct{ Log *log.Entry }
func New() (h *handler) { return &handler{Log: log.WithFields(log.Fields{"test": "FAIL"})} }
func (h *handler) index(w http.ResponseWriter, r *http.Request) {
h.Log.Info("index")
fmt.Fprintf(w, "hello")
}
func (h *handler) about(w http.ResponseWriter, r *http.Request) {
h.Log.Info("about")
fmt.Fprintf(w, "about")
}
func main() {
h := New()
app := mux.NewRouter()
app.HandleFunc("/", h.index)
app.HandleFunc("/about", h.about)
app.Use(h.loggingMiddleware)
if err := http.ListenAndServe(":"+os.Getenv("PORT"), app); err != nil {
log.WithError(err).Fatal("error listening")
}
}
如果我不能证明它具有竞赛条件,我可以假定设置h.Log
是安全的吗?
If I can't prove it has a race condition, can I assume setting h.Log
is safe?
推荐答案
有一种编程方式,为此您必须做两件事:
There is a programmatic way, for which you have to do 2 things:
- 再现民主状况
- 并在启动
go
工具时使用-race
选项
- reproduce the racy condition
- and use the
-race
option when launching thego
tool
最好是为它编写一个单元测试,因此该测试也是可重复的,并且可以在每次构建/部署时自动运行/检查.
Best is if you write a unit test for it, so the test is also reproducible, and run / checked automatically on each build / deploy.
好,那么如何重现呢?
只需编写一个测试,该测试将有意地在没有同步的情况下启动两个goroutine,一个调用index
处理程序,一个调用about
处理程序,这就是触发竞赛检测器的原因.
Simply write a test which launches 2 goroutines, one which calls the index
handler, and one which calls the about
handler, deliberately without synchronization, this is what triggers the race detector.
使用 net/http/httptest
包可以轻松测试处理程序. httptest.NewServer()
会为您准备就绪的服务器,并与处理程序武装"在一起你传递给它.
Use the net/http/httptest
package to easily test handlers. httptest.NewServer()
hands you a ready server, "armed" with the handler you pass to it.
这是一个简单的测试示例,它将触发竞争条件.将其放在main.go
文件旁边的名为main_test.go
的文件中:
Here's a simple test example that will trigger the race condition. Put it in a file named main_test.go
, next to your main.go
file:
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"github.com/gorilla/mux"
)
func TestRace(t *testing.T) {
h := New()
app := mux.NewRouter()
app.HandleFunc("/", h.index)
app.HandleFunc("/about", h.about)
app.Use(h.loggingMiddleware)
server := httptest.NewServer(app)
defer server.Close()
wg := &sync.WaitGroup{}
for _, path := range []string{"/", "/about"} {
path := path
wg.Add(1)
go func() {
defer wg.Done()
req, err := http.NewRequest(http.MethodGet, server.URL+path, nil)
fmt.Println(server.URL + path)
if err != nil {
panic(err)
}
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
}()
}
wg.Wait()
}
您必须使用它运行
go test -race
示例输出为:
http://127.0.0.1:33007/
http://127.0.0.1:33007/about
==================
WARNING: DATA RACE
Write at 0x00c000098030 by goroutine 17:
play.(*handler).loggingMiddleware.func1()
/home/icza/tmp/gows/src/play/main.go:16 +0x1ce
net/http.HandlerFunc.ServeHTTP()
/usr/local/go/src/net/http/server.go:1964 +0x51
github.com/gorilla/mux.(*Router).ServeHTTP()
/home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
net/http.serverHandler.ServeHTTP()
/usr/local/go/src/net/http/server.go:2741 +0xc4
net/http.(*conn).serve()
/usr/local/go/src/net/http/server.go:1847 +0x80a
Previous write at 0x00c000098030 by goroutine 16:
play.(*handler).loggingMiddleware.func1()
/home/icza/tmp/gows/src/play/main.go:16 +0x1ce
net/http.HandlerFunc.ServeHTTP()
/usr/local/go/src/net/http/server.go:1964 +0x51
github.com/gorilla/mux.(*Router).ServeHTTP()
/home/icza/tmp/gows/src/github.com/gorilla/mux/mux.go:212 +0x12e
net/http.serverHandler.ServeHTTP()
/usr/local/go/src/net/http/server.go:2741 +0xc4
net/http.(*conn).serve()
/usr/local/go/src/net/http/server.go:1847 +0x80a
Goroutine 17 (running) created at:
net/http.(*Server).Serve()
/usr/local/go/src/net/http/server.go:2851 +0x4c5
net/http/httptest.(*Server).goServe.func1()
/usr/local/go/src/net/http/httptest/server.go:280 +0xac
Goroutine 16 (running) created at:
net/http.(*Server).Serve()
/usr/local/go/src/net/http/server.go:2851 +0x4c5
net/http/httptest.(*Server).goServe.func1()
/usr/local/go/src/net/http/httptest/server.go:280 +0xac
==================
2019/01/06 14:58:50 info index method=GET requestURI=/
2019/01/06 14:58:50 info about method=GET requestURI=/about
--- FAIL: TestRace (0.00s)
testing.go:771: race detected during execution of test
FAIL
exit status 1
FAIL play 0.011s
测试失败,表明存在数据争用.
The test fails, showing that there are data races.
注释:
与sync.WaitGroup
的同步是等待2个启动的goroutine,而不是同步对处理程序记录器的访问(这会导致数据争用).这样一来,如果您解决了数据争用问题,则测试将正常运行并结束(等待2个已启动的test-goroutines完成).
The synchronization with the sync.WaitGroup
is to wait for the 2 launched goroutines, it is not to synchronize access to the handler's logger (which causes the data race). This is so if you fix the data race, the test will run and end properly (waiting for the 2 launched test-goroutines to complete).
这篇关于如何以编程方式证明此代码具有竞争条件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!