@@ -12,6 +12,7 @@ import (
1212 "net/url"
1313 "reflect"
1414 "strings"
15+ "sync"
1516 "testing"
1617 "time"
1718
@@ -366,7 +367,7 @@ func TestTimeoutWithFullEchoStack(t *testing.T) {
366367 t .Run (tc .name , func (t * testing.T ) {
367368 e := echo .New ()
368369
369- buf := new (bytes. Buffer )
370+ buf := new (coroutineSafeBuffer )
370371 e .Logger .SetOutput (buf )
371372
372373 // NOTE: timeout middleware is first as it changes Response.Writer and causes data race for logger middleware if it is not first
@@ -419,6 +420,36 @@ func TestTimeoutWithFullEchoStack(t *testing.T) {
419420 }
420421}
421422
423+ // as we are spawning multiple coroutines - one for http server, one for request, one by timeout middleware, one by testcase
424+ // we are accessing logger (writing/reading) from multiple coroutines and causing dataraces (most often reported on macos)
425+ // we could be writing to logger in logger middleware and at the same time our tests is getting logger buffer contents
426+ // in testcase coroutine.
427+ type coroutineSafeBuffer struct {
428+ bytes.Buffer
429+ lock sync.RWMutex
430+ }
431+
432+ func (b * coroutineSafeBuffer ) Write (p []byte ) (n int , err error ) {
433+ b .lock .Lock ()
434+ defer b .lock .Unlock ()
435+
436+ return b .Buffer .Write (p )
437+ }
438+
439+ func (b * coroutineSafeBuffer ) Bytes () []byte {
440+ b .lock .RLock ()
441+ defer b .lock .RUnlock ()
442+
443+ return b .Buffer .Bytes ()
444+ }
445+
446+ func (b * coroutineSafeBuffer ) String () string {
447+ b .lock .RLock ()
448+ defer b .lock .RUnlock ()
449+
450+ return b .Buffer .String ()
451+ }
452+
422453func startServer (e * echo.Echo ) (* http.Server , string , error ) {
423454 l , err := net .Listen ("tcp" , ":0" )
424455 if err != nil {
0 commit comments