diff options
Diffstat (limited to 'doc/articles')
-rw-r--r-- | doc/articles/race_detector.html | 388 | ||||
-rw-r--r-- | doc/articles/wiki/Makefile | 10 | ||||
-rw-r--r-- | doc/articles/wiki/final.go | 23 | ||||
-rw-r--r-- | doc/articles/wiki/index.html | 2 | ||||
-rwxr-xr-x | doc/articles/wiki/test.bash | 26 |
5 files changed, 432 insertions, 17 deletions
diff --git a/doc/articles/race_detector.html b/doc/articles/race_detector.html new file mode 100644 index 000000000..282db8ba4 --- /dev/null +++ b/doc/articles/race_detector.html @@ -0,0 +1,388 @@ +<!--{ + "Title": "Data Race Detector", + "Template": true +}--> + +<h2 id="Introduction">Introduction</h2> + +<p> +Data races are among the most common and hardest to debug types of bugs in concurrent systems. +A data race occurs when two goroutines access the same variable concurrently and at least one of the accesses is a write. +See the <a href="/ref/mem/">The Go Memory Model</a> for details. +</p> + +<p> +Here is an example of a data race that can lead to crashes and memory corruption: +</p> + +<pre> +func main() { + c := make(chan bool) + m := make(map[string]string) + go func() { + m["1"] = "a" // First conflicting access. + c <- true + }() + m["2"] = "b" // Second conflicting access. + <-c + for k, v := range m { + fmt.Println(k, v) + } +} +</pre> + +<h2 id="Usage">Usage</h2> + +<p> +To help diagnose such bugs, Go includes a built-in data race detector. +To use it, add the <code>-race</code> flag to the go command: +</p> + +<pre> +$ go test -race mypkg // to test the package +$ go run -race mysrc.go // to run the source file +$ go build -race mycmd // to build the command +$ go install -race mypkg // to install the package +</pre> + +<h2 id="Report_Format">Report Format</h2> + +<p> +When the race detector finds a data race in the program, it prints a report. +The report contains stack traces for conflicting accesses, as well as stacks where the involved goroutines were created. +Here is an example: +</p> + +<pre> +WARNING: DATA RACE +Read by goroutine 185: + net.(*pollServer).AddFD() + src/pkg/net/fd_unix.go:89 +0x398 + net.(*pollServer).WaitWrite() + src/pkg/net/fd_unix.go:247 +0x45 + net.(*netFD).Write() + src/pkg/net/fd_unix.go:540 +0x4d4 + net.(*conn).Write() + src/pkg/net/net.go:129 +0x101 + net.func·060() + src/pkg/net/timeout_test.go:603 +0xaf + +Previous write by goroutine 184: + net.setWriteDeadline() + src/pkg/net/sockopt_posix.go:135 +0xdf + net.setDeadline() + src/pkg/net/sockopt_posix.go:144 +0x9c + net.(*conn).SetDeadline() + src/pkg/net/net.go:161 +0xe3 + net.func·061() + src/pkg/net/timeout_test.go:616 +0x3ed + +Goroutine 185 (running) created at: + net.func·061() + src/pkg/net/timeout_test.go:609 +0x288 + +Goroutine 184 (running) created at: + net.TestProlongTimeout() + src/pkg/net/timeout_test.go:618 +0x298 + testing.tRunner() + src/pkg/testing/testing.go:301 +0xe8 +</pre> + +<h2 id="Options">Options</h2> + +<p> +The <code>GORACE</code> environment variable sets race detector options. +The format is: +</p> + +<pre> +GORACE="option1=val1 option2=val2" +</pre> + +<p> +The options are: +</p> + +<ul> +<li> +<code>log_path</code> (default <code>stderr</code>): The race detector writes +its report to a file named <code>log_path.<em>pid</em></code>. +The special names <code>stdout</code> +and <code>stderr</code> cause reports to be written to standard output and +standard error, respectively. +</li> + +<li> +<code>exitcode</code> (default <code>66</code>): The exit status to use when +exiting after a detected race. +</li> + +<li> +<code>strip_path_prefix</code> (default <code>""</code>): Strip this prefix +from all reported file paths, to make reports more concise. +</li> + +<li> +<code>history_size</code> (default <code>1</code>): The per-goroutine memory +access history is <code>32K * 2**history_size elements</code>. +Increasing this value can avoid a "failed to restore the stack" error in reports, at the +cost of increased memory usage. +</li> + +<li> +<code>halt_on_error</code> (default <code>0</code>): Controls whether the program +exits after reporting first data race. +</li> +</ul> + +<p> +Example: +</p> + +<pre> +$ GORACE="log_path=/tmp/race/report strip_path_prefix=/my/go/sources/" go test -race +</pre> + +<h2 id="Excluding_Tests">Excluding Tests</h2> + +<p> +When you build with <code>-race</code> flag, the <code>go</code> command defines additional +<a href="/pkg/go/build/#hdr-Build_Constraints">build tag</a> <code>race</code>. +You can use the tag to exclude some code and tests when running the race detector. +Some examples: +</p> + +<pre> +// +build !race + +package foo + +// The test contains a data race. See issue 123. +func TestFoo(t *testing.T) { + // ... +} + +// The test fails under the race detector due to timeouts. +func TestBar(t *testing.T) { + // ... +} + +// The test takes too long under the race detector. +func TestBaz(t *testing.T) { + // ... +} +</pre> + +<h2 id="How_To_Use">How To Use</h2> + +<p> +To start, run your tests using the race detector (<code>go test -race</code>). +The race detector only finds races that happen at runtime, so it can't find +races in code paths that are not executed. +If your tests have incomplete coverage, +you may find more races by running a binary built with <code>-race</code> under a realistic +workload. +</p> + +<h2 id="Typical_Data_Races">Typical Data Races</h2> + +<p> +Here are some typical data races. All of them can be detected with the race detector. +</p> + +<h3 id="Race_on_loop_counter">Race on loop counter</h3> + +<pre> +func main() { + var wg sync.WaitGroup + wg.Add(5) + for i := 0; i < 5; i++ { + go func() { + fmt.Println(i) // Not the 'i' you are looking for. + wg.Done() + }() + } + wg.Wait() +} +</pre> + +<p> +The variable <code>i</code> in the function literal is the same variable used by the loop, so +the read in the goroutine races with the loop increment. +(This program typically prints 55555, not 01234.) +The program can be fixed by making a copy of the variable: +</p> + +<pre> +func main() { + var wg sync.WaitGroup + wg.Add(5) + for i := 0; i < 5; i++ { + go func(j int) { + fmt.Println(j) // Good. Read local copy of the loop counter. + wg.Done() + }(i) + } + wg.Wait() +} +</pre> + +<h3 id="Accidentally_shared_variable">Accidentally shared variable</h3> + +<pre> +// ParallelWrite writes data to file1 and file2, returns the errors. +func ParallelWrite(data []byte) chan error { + res := make(chan error, 2) + f1, err := os.Create("file1") + if err != nil { + res <- err + } else { + go func() { + // This err is shared with the main goroutine, + // so the write races with the write below. + _, err = f1.Write(data) + res <- err + f1.Close() + }() + } + f2, err := os.Create("file2") // The second conflicting write to err. + if err != nil { + res <- err + } else { + go func() { + _, err = f2.Write(data) + res <- err + f2.Close() + }() + } + return res +} +</pre> + +<p> +The fix is to introduce new variables in the goroutines (note the use of <code>:=</code>): +</p> + +<pre> + ... + _, err := f1.Write(data) + ... + _, err := f2.Write(data) + ... +</pre> + +<h3 id="Unprotected_global_variable">Unprotected global variable</h3> + +<p> +If the following code is called from several goroutines, it leads to races on the <code>service</code> map. +Concurrent reads and writes of the same map are not safe: +</p> + +<pre> +var service map[string]net.Addr + +func RegisterService(name string, addr net.Addr) { + service[name] = addr +} + +func LookupService(name string) net.Addr { + return service[name] +} +</pre> + +<p> +To make the code safe, protect the accesses with a mutex: +</p> + +<pre> +var ( + service map[string]net.Addr + serviceMu sync.Mutex +) + +func RegisterService(name string, addr net.Addr) { + serviceMu.Lock() + defer serviceMu.Unlock() + service[name] = addr +} + +func LookupService(name string) net.Addr { + serviceMu.Lock() + defer serviceMu.Unlock() + return service[name] +} +</pre> + +<h3 id="Primitive_unprotected_variable">Primitive unprotected variable</h3> + +<p> +Data races can happen on variables of primitive types as well (<code>bool</code>, <code>int</code>, <code>int64</code>, etc.), +as in this example: +</p> + +<pre> +type Watchdog struct{ last int64 } + +func (w *Watchdog) KeepAlive() { + w.last = time.Now().UnixNano() // First conflicting access. +} + +func (w *Watchdog) Start() { + go func() { + for { + time.Sleep(time.Second) + // Second conflicting access. + if w.last < time.Now().Add(-10*time.Second).UnixNano() { + fmt.Println("No keepalives for 10 seconds. Dying.") + os.Exit(1) + } + } + }() +} +</pre> + +<p> +Even such "innocent" data races can lead to hard-to-debug problems caused by +non-atomicity of the memory accesses, +interference with compiler optimizations, +or reordering issues accessing processor memory . +</p> + +<p> +A typical fix for this race is to use a channel or a mutex. +To preserve the lock-free behavior, one can also use the +<a href="/pkg/sync/atomic/"><code>sync/atomic</code></a> package. +</p> + +<pre> +type Watchdog struct{ last int64 } + +func (w *Watchdog) KeepAlive() { + atomic.StoreInt64(&w.last, time.Now().UnixNano()) +} + +func (w *Watchdog) Start() { + go func() { + for { + time.Sleep(time.Second) + if atomic.LoadInt64(&w.last) < time.Now().Add(-10*time.Second).UnixNano() { + fmt.Println("No keepalives for 10 seconds. Dying.") + os.Exit(1) + } + } + }() +} +</pre> + +<h2 id="Supported_Systems">Supported Systems</h2> + +<p> +The race detector runs on <code>darwin/amd64</code>, <code>linux/amd64</code>, and <code>windows/amd64</code>. +</p> + +<h2 id="Runtime_Overheads">Runtime Overhead</h2> + +<p> +The cost of race detection varies by program, but for a typical program, memory +usage may increase by 5-10x and execution time by 2-20x. +</p> diff --git a/doc/articles/wiki/Makefile b/doc/articles/wiki/Makefile deleted file mode 100644 index e40b1311e..000000000 --- a/doc/articles/wiki/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright 2010 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -all: index.html - -CLEANFILES:=get.bin final-test.bin a.out - -clean: - rm -f $(CLEANFILES) diff --git a/doc/articles/wiki/final.go b/doc/articles/wiki/final.go index f15794d66..d84c1ffb2 100644 --- a/doc/articles/wiki/final.go +++ b/doc/articles/wiki/final.go @@ -5,12 +5,19 @@ package main import ( + "flag" "html/template" "io/ioutil" + "log" + "net" "net/http" "regexp" ) +var ( + addr = flag.Bool("addr", false, "find open address and print to final-port.txt") +) + type Page struct { Title string Body []byte @@ -81,8 +88,24 @@ func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.Handl } func main() { + flag.Parse() http.HandleFunc("/view/", makeHandler(viewHandler)) http.HandleFunc("/edit/", makeHandler(editHandler)) http.HandleFunc("/save/", makeHandler(saveHandler)) + + if *addr { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + log.Fatal(err) + } + err = ioutil.WriteFile("final-port.txt", []byte(l.Addr().String()), 0644) + if err != nil { + log.Fatal(err) + } + s := &http.Server{} + s.Serve(l) + return + } + http.ListenAndServe(":8080", nil) } diff --git a/doc/articles/wiki/index.html b/doc/articles/wiki/index.html index 7bf7213e8..b6b080df9 100644 --- a/doc/articles/wiki/index.html +++ b/doc/articles/wiki/index.html @@ -466,7 +466,7 @@ header to the HTTP response. <p> The function <code>saveHandler</code> will handle the submission of forms located on the edit pages. After uncommenting the related line in -<code>main</code>, let's implement the the handler: +<code>main</code>, let's implement the handler: </p> {{code "doc/articles/wiki/final-template.go" `/^func saveHandler/` `/^}/`}} diff --git a/doc/articles/wiki/test.bash b/doc/articles/wiki/test.bash index 54a632c30..2997f1680 100755 --- a/doc/articles/wiki/test.bash +++ b/doc/articles/wiki/test.bash @@ -7,10 +7,12 @@ set -e wiki_pid= cleanup() { kill $wiki_pid - rm -f test_*.out Test.txt final-test.bin final-test.go a.out get.bin + rm -f test_*.out Test.txt final.bin final-port.txt a.out get.bin } trap cleanup 0 INT +rm -f get.bin final.bin a.out + # If called with -all, check that all code snippets compile. if [ "$1" == "-all" ]; then for fn in *.go; do @@ -19,13 +21,25 @@ if [ "$1" == "-all" ]; then fi go build -o get.bin get.go -addr=$(./get.bin -addr) -sed s/:8080/$addr/ < final.go > final-test.go -go build -o final-test.bin final-test.go -(./final-test.bin) & +go build -o final.bin final.go +(./final.bin --addr) & wiki_pid=$! -./get.bin --wait_for_port=5s http://$addr/edit/Test > test_edit.out +l=0 +while [ ! -f ./final-port.txt ] +do + l=$(($l+1)) + if [ "$l" -gt 5 ] + then + echo "port not available within 5 seconds" + exit 1 + break + fi + sleep 1 +done + +addr=$(cat final-port.txt) +./get.bin http://$addr/edit/Test > test_edit.out diff -u test_edit.out test_edit.good ./get.bin -post=body=some%20content http://$addr/save/Test > test_save.out diff -u test_save.out test_view.good # should be the same as viewing |