Merge lp:~dave-cheney/gocheck/003-serialise-log-writes into lp:gocheck

Proposed by Dave Cheney
Status: Merged
Merged at revision: 83
Proposed branch: lp:~dave-cheney/gocheck/003-serialise-log-writes
Merge into: lp:gocheck
Diff against target: 159 lines (+62/-16)
2 files modified
gocheck.go (+39/-14)
helpers_test.go (+23/-2)
To merge this branch: bzr merge lp:~dave-cheney/gocheck/003-serialise-log-writes
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Pending
Review via email: mp+138378@code.launchpad.net

Description of the change

gocheck: serialize log writes

Fixes LP #1084878.

The race detector cannot profile gocheck tested code due to this race.

https://codereview.appspot.com/6862050/

To post a comment you must log in.
82. By Dave Cheney

wait for all workers to finish

Revision history for this message
Roger Peppe (rogpeppe) wrote :

LGTM with a few thoughts below

https://codereview.appspot.com/6862050/diff/5/gocheck.go
File gocheck.go (right):

https://codereview.appspot.com/6862050/diff/5/gocheck.go#newcode620
gocheck.go:620: type serialisedLogger struct {
s/serialisedLogger/logger/
and lose the interface type?

https://codereview.appspot.com/6862050/diff/5/helpers_test.go
File helpers_test.go (right):

https://codereview.appspot.com/6862050/diff/5/helpers_test.go#newcode441
helpers_test.go:441: go func(i int) {
personally i think that:
    i := i
reads more naturally than declaring a parameter and passing it.

https://codereview.appspot.com/6862050/diff/5/helpers_test.go#newcode449
helpers_test.go:449: start.Done()
this seems a slightly inappropriate use of sync.WaitGroup. i'd use a
channel for this and close it to start everything.

https://codereview.appspot.com/6862050/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6862050/diff/5/gocheck.go
File gocheck.go (right):

https://codereview.appspot.com/6862050/diff/5/gocheck.go#newcode145
gocheck.go:145: func (c *C) writeLog(buf []byte) {
Isn't it enough to add a lock to this method?

https://codereview.appspot.com/6862050/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

https://codereview.appspot.com/6862050/diff/5/helpers_test.go
File helpers_test.go (right):

https://codereview.appspot.com/6862050/diff/5/helpers_test.go#newcode449
helpers_test.go:449: start.Done()
On 2012/12/06 06:51:57, rog wrote:
> this seems a slightly inappropriate use of sync.WaitGroup. i'd use a
channel for
> this and close it to start everything.

Why is it inappropriate? It feels very clean and clear to me.
Inappropriate != I haven't seen it before.

https://codereview.appspot.com/6862050/

83. By Dave Cheney

responding to review feedback

Revision history for this message
William Reade (fwereade) wrote :
84. By Dave Cheney

reverting to previous approach

Revision history for this message
Roger Peppe (rogpeppe) wrote :

On 2012/12/11 02:34:03, dfc wrote:
> Please take a look.

LGTM

https://codereview.appspot.com/6862050/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

LGTM

https://codereview.appspot.com/6862050/diff/27001/gocheck.go
File gocheck.go (right):

https://codereview.appspot.com/6862050/diff/27001/gocheck.go#newcode60
gocheck.go:60: writeLock sync.Mutex // protects logb from concurrent
writes
That's now unused.

https://codereview.appspot.com/6862050/diff/27001/gocheck.go#newcode74
gocheck.go:74: // logger concurrency safe byte.Buffer
logger is a

https://codereview.appspot.com/6862050/diff/27001/helpers_test.go
File helpers_test.go (right):

https://codereview.appspot.com/6862050/diff/27001/helpers_test.go#newcode450
helpers_test.go:450: stop.Wait()
Shouldn't be hard to have an assertion here checking that there wasn't
corruption.

https://codereview.appspot.com/6862050/

Revision history for this message
Gustavo Niemeyer (niemeyer) wrote :

*** Submitted:

gocheck: serialize log writes

Fixes LP #1084878.

The race detector cannot profile gocheck tested code due to this race.

R=rog, niemeyer, fwereade
CC=
https://codereview.appspot.com/6862050

https://codereview.appspot.com/6862050/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'gocheck.go'
2--- gocheck.go 2012-07-22 21:58:00 +0000
3+++ gocheck.go 2012-12-11 02:33:21 +0000
4@@ -54,15 +54,16 @@
5 }
6
7 type C struct {
8- method *methodType
9- kind funcKind
10- status funcStatus
11- logb *bytes.Buffer
12- logw io.Writer
13- done chan *C
14- reason string
15- mustFail bool
16- tempDir *tempDir
17+ method *methodType
18+ kind funcKind
19+ status funcStatus
20+ writeLock sync.Mutex // protects logb from concurrent writes
21+ logb *logger
22+ logw io.Writer
23+ done chan *C
24+ reason string
25+ mustFail bool
26+ tempDir *tempDir
27 timer
28 }
29
30@@ -70,6 +71,30 @@
31 runtime.Goexit()
32 }
33
34+// logger concurrency safe byte.Buffer
35+type logger struct {
36+ sync.Mutex
37+ writer bytes.Buffer
38+}
39+
40+func (l *logger) Write(buf []byte) (int, error) {
41+ l.Lock()
42+ defer l.Unlock()
43+ return l.writer.Write(buf)
44+}
45+
46+func (l *logger) WriteTo(w io.Writer) (int64, error) {
47+ l.Lock()
48+ defer l.Unlock()
49+ return l.writer.WriteTo(w)
50+}
51+
52+func (l *logger) String() string {
53+ l.Lock()
54+ defer l.Unlock()
55+ return l.writer.String()
56+}
57+
58 // -----------------------------------------------------------------------
59 // Handling of temporary files and directories.
60
61@@ -584,13 +609,13 @@
62
63 // Create a call object with the given suite method, and fork a
64 // goroutine with the provided dispatcher for running it.
65-func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, logb *bytes.Buffer, dispatcher func(c *C)) *C {
66+func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, logb *logger, dispatcher func(c *C)) *C {
67 var logw io.Writer
68 if runner.output.Stream {
69 logw = runner.output
70 }
71 if logb == nil {
72- logb = bytes.NewBuffer(nil)
73+ logb = new(logger)
74 }
75 c := &C{
76 method: method,
77@@ -611,7 +636,7 @@
78 }
79
80 // Same as forkCall(), but wait for call to finish before returning.
81-func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, logb *bytes.Buffer, dispatcher func(c *C)) *C {
82+func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, logb *logger, dispatcher func(c *C)) *C {
83 c := runner.forkCall(method, kind, logb, dispatcher)
84 <-c.done
85 return c
86@@ -654,7 +679,7 @@
87 // goroutine like all suite methods, but this method will not return
88 // while the fixture goroutine is not done, because the fixture must be
89 // run in a desired order.
90-func (runner *suiteRunner) runFixture(method *methodType, logb *bytes.Buffer) *C {
91+func (runner *suiteRunner) runFixture(method *methodType, logb *logger) *C {
92 if method != nil {
93 c := runner.runFunc(method, fixtureKd, logb, func(c *C) {
94 c.ResetTimer()
95@@ -670,7 +695,7 @@
96 // Run the fixture method with runFixture(), but panic with a fixturePanic{}
97 // in case the fixture method panics. This makes it easier to track the
98 // fixture panic together with other call panics within forkTest().
99-func (runner *suiteRunner) runFixtureWithPanic(method *methodType, logb *bytes.Buffer, skipped *bool) *C {
100+func (runner *suiteRunner) runFixtureWithPanic(method *methodType, logb *logger, skipped *bool) *C {
101 if *skipped {
102 return nil
103 }
104
105=== modified file 'helpers_test.go'
106--- helpers_test.go 2012-02-25 18:28:36 +0000
107+++ helpers_test.go 2012-12-11 02:33:21 +0000
108@@ -7,6 +7,8 @@
109 "launchpad.net/gocheck"
110 "os"
111 "reflect"
112+ "runtime"
113+ "sync"
114 )
115
116 var helpersS = gocheck.Suite(&HelpersS{})
117@@ -126,7 +128,7 @@
118 testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log,
119 func() interface{} {
120 // Nice leading comment.
121- return c.Check(1, checker, 2) // Hello there
122+ return c.Check(1, checker, 2) // Hello there
123 })
124 }
125
126@@ -379,7 +381,6 @@
127 })
128 }
129
130-
131 // -----------------------------------------------------------------------
132 // MakeDir() tests.
133
134@@ -429,6 +430,26 @@
135 return false
136 }
137
138+// Concurrent logging should not corrupt the underling buffer.
139+// Use go test -race to detect the race in this test.
140+func (s *HelpersS) TestConcurrentLogging(c *gocheck.C) {
141+ defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU()))
142+ var start, stop sync.WaitGroup
143+ start.Add(1)
144+ for i, n := 0, runtime.NumCPU()*2; i < n; i++ {
145+ stop.Add(1)
146+ go func(i int) {
147+ start.Wait()
148+ for j := 0; j < 30; j++ {
149+ c.Logf("Worker %d: line %d", i, j)
150+ }
151+ stop.Done()
152+ }(i)
153+ }
154+ start.Done()
155+ stop.Wait()
156+}
157+
158 // -----------------------------------------------------------------------
159 // A couple of helper functions to test helper functions. :-)
160

Subscribers

People subscribed via source and target branches