Browse Source

Make Profile copying and encoding thread-safe. (#232)

Added some multi-threaded tests that allow the race detector
to detect race conditions in pprof internals.
Sanjay Ghemawat 7 years ago
parent
commit
f0300aaaf0
3 changed files with 61 additions and 10 deletions
  1. 18
    0
      internal/driver/webui_test.go
  2. 16
    10
      profile/profile.go
  3. 27
    0
      profile/profile_test.go

+ 18
- 0
internal/driver/webui_test.go View File

@@ -23,6 +23,7 @@ import (
23 23
 	"net/url"
24 24
 	"os/exec"
25 25
 	"regexp"
26
+	"sync"
26 27
 	"testing"
27 28
 
28 29
 	"github.com/google/pprof/internal/plugin"
@@ -101,6 +102,23 @@ func TestWebInterface(t *testing.T) {
101 102
 		}
102 103
 	}
103 104
 
105
+	// Also fetch all the test case URLs in parallel to test thread
106
+	// safety when run under the race detector.
107
+	var wg sync.WaitGroup
108
+	for _, c := range testcases {
109
+		if c.needDot && !haveDot {
110
+			continue
111
+		}
112
+		path := server.URL + c.path
113
+		for count := 0; count < 2; count++ {
114
+			wg.Add(1)
115
+			go func() {
116
+				http.Get(path)
117
+				wg.Done()
118
+			}()
119
+		}
120
+	}
121
+	wg.Wait()
104 122
 }
105 123
 
106 124
 // Implement fake object file support.

+ 16
- 10
profile/profile.go View File

@@ -26,6 +26,7 @@ import (
26 26
 	"regexp"
27 27
 	"sort"
28 28
 	"strings"
29
+	"sync"
29 30
 	"time"
30 31
 )
31 32
 
@@ -47,6 +48,10 @@ type Profile struct {
47 48
 	PeriodType    *ValueType
48 49
 	Period        int64
49 50
 
51
+	// The following fields are modified during encoding and copying,
52
+	// so are protected by a Mutex.
53
+	encodeMu sync.Mutex
54
+
50 55
 	commentX           []int64
51 56
 	dropFramesX        int64
52 57
 	keepFramesX        int64
@@ -296,21 +301,25 @@ func (p *Profile) updateLocationMapping(from, to *Mapping) {
296 301
 	}
297 302
 }
298 303
 
299
-// Write writes the profile as a gzip-compressed marshaled protobuf.
300
-func (p *Profile) Write(w io.Writer) error {
304
+func serialize(p *Profile) []byte {
305
+	p.encodeMu.Lock()
301 306
 	p.preEncode()
302 307
 	b := marshal(p)
308
+	p.encodeMu.Unlock()
309
+	return b
310
+}
311
+
312
+// Write writes the profile as a gzip-compressed marshaled protobuf.
313
+func (p *Profile) Write(w io.Writer) error {
303 314
 	zw := gzip.NewWriter(w)
304 315
 	defer zw.Close()
305
-	_, err := zw.Write(b)
316
+	_, err := zw.Write(serialize(p))
306 317
 	return err
307 318
 }
308 319
 
309 320
 // WriteUncompressed writes the profile as a marshaled protobuf.
310 321
 func (p *Profile) WriteUncompressed(w io.Writer) error {
311
-	p.preEncode()
312
-	b := marshal(p)
313
-	_, err := w.Write(b)
322
+	_, err := w.Write(serialize(p))
314 323
 	return err
315 324
 }
316 325
 
@@ -605,11 +614,8 @@ func (m *Mapping) Unsymbolizable() bool {
605 614
 
606 615
 // Copy makes a fully independent copy of a profile.
607 616
 func (p *Profile) Copy() *Profile {
608
-	p.preEncode()
609
-	b := marshal(p)
610
-
611 617
 	pp := &Profile{}
612
-	if err := unmarshal(b, pp); err != nil {
618
+	if err := unmarshal(serialize(p), pp); err != nil {
613 619
 		panic(err)
614 620
 	}
615 621
 	if err := pp.postDecode(); err != nil {

+ 27
- 0
profile/profile_test.go View File

@@ -21,6 +21,7 @@ import (
21 21
 	"path/filepath"
22 22
 	"regexp"
23 23
 	"strings"
24
+	"sync"
24 25
 	"testing"
25 26
 
26 27
 	"github.com/google/pprof/internal/proftest"
@@ -694,3 +695,29 @@ func TestSetMain(t *testing.T) {
694 695
 		t.Errorf("got %s for main", testProfile1.Mapping[0].File)
695 696
 	}
696 697
 }
698
+
699
+// parallel runs n copies of fn in parallel.
700
+func parallel(n int, fn func()) {
701
+	var wg sync.WaitGroup
702
+	wg.Add(n)
703
+	for i := 0; i < n; i++ {
704
+		go func() {
705
+			fn()
706
+			wg.Done()
707
+		}()
708
+	}
709
+	wg.Wait()
710
+}
711
+
712
+func TestThreadSafety(t *testing.T) {
713
+	src := testProfile1.Copy()
714
+	parallel(4, func() { src.Copy() })
715
+	parallel(4, func() {
716
+		var b bytes.Buffer
717
+		src.WriteUncompressed(&b)
718
+	})
719
+	parallel(4, func() {
720
+		var b bytes.Buffer
721
+		src.Write(&b)
722
+	})
723
+}