Skip to content

Commit 75663bb

Browse files
authored
Merge pull request #408 from pohly/klog-flush-sync-fix
data race: avoid unprotected access to sb.file
2 parents 16c7d26 + 2327d4c commit 75663bb

File tree

2 files changed

+81
-61
lines changed

2 files changed

+81
-61
lines changed

klog.go

+25-52
Original file line numberDiff line numberDiff line change
@@ -404,13 +404,6 @@ func (t *traceLocation) Set(value string) error {
404404
return nil
405405
}
406406

407-
// flushSyncWriter is the interface satisfied by logging destinations.
408-
type flushSyncWriter interface {
409-
Flush() error
410-
Sync() error
411-
io.Writer
412-
}
413-
414407
var logging loggingT
415408
var commandLine flag.FlagSet
416409

@@ -486,7 +479,7 @@ type settings struct {
486479
// Access to all of the following fields must be protected via a mutex.
487480

488481
// file holds writer for each of the log types.
489-
file [severity.NumSeverity]flushSyncWriter
482+
file [severity.NumSeverity]io.Writer
490483
// flushInterval is the interval for periodic flushing. If zero,
491484
// the global default will be used.
492485
flushInterval time.Duration
@@ -831,32 +824,12 @@ func (l *loggingT) printS(err error, s severity.Severity, depth int, msg string,
831824
buffer.PutBuffer(b)
832825
}
833826

834-
// redirectBuffer is used to set an alternate destination for the logs
835-
type redirectBuffer struct {
836-
w io.Writer
837-
}
838-
839-
func (rb *redirectBuffer) Sync() error {
840-
return nil
841-
}
842-
843-
func (rb *redirectBuffer) Flush() error {
844-
return nil
845-
}
846-
847-
func (rb *redirectBuffer) Write(bytes []byte) (n int, err error) {
848-
return rb.w.Write(bytes)
849-
}
850-
851827
// SetOutput sets the output destination for all severities
852828
func SetOutput(w io.Writer) {
853829
logging.mu.Lock()
854830
defer logging.mu.Unlock()
855831
for s := severity.FatalLog; s >= severity.InfoLog; s-- {
856-
rb := &redirectBuffer{
857-
w: w,
858-
}
859-
logging.file[s] = rb
832+
logging.file[s] = w
860833
}
861834
}
862835

@@ -868,10 +841,7 @@ func SetOutputBySeverity(name string, w io.Writer) {
868841
if !ok {
869842
panic(fmt.Sprintf("SetOutputBySeverity(%q): unrecognized severity name", name))
870843
}
871-
rb := &redirectBuffer{
872-
w: w,
873-
}
874-
logging.file[sev] = rb
844+
logging.file[sev] = w
875845
}
876846

877847
// LogToStderr sets whether to log exclusively to stderr, bypassing outputs
@@ -1011,8 +981,8 @@ func (l *loggingT) exit(err error) {
1011981
logExitFunc(err)
1012982
return
1013983
}
1014-
files := l.flushAll()
1015-
l.syncAll(files)
984+
needToSync := l.flushAll()
985+
l.syncAll(needToSync)
1016986
OsExit(2)
1017987
}
1018988

@@ -1029,10 +999,6 @@ type syncBuffer struct {
1029999
maxbytes uint64 // The max number of bytes this syncBuffer.file can hold before cleaning up.
10301000
}
10311001

1032-
func (sb *syncBuffer) Sync() error {
1033-
return sb.file.Sync()
1034-
}
1035-
10361002
// CalculateMaxSize returns the real max size in bytes after considering the default max size and the flag options.
10371003
func CalculateMaxSize() uint64 {
10381004
if logging.logFile != "" {
@@ -1224,37 +1190,44 @@ func StartFlushDaemon(interval time.Duration) {
12241190
// lockAndFlushAll is like flushAll but locks l.mu first.
12251191
func (l *loggingT) lockAndFlushAll() {
12261192
l.mu.Lock()
1227-
files := l.flushAll()
1193+
needToSync := l.flushAll()
12281194
l.mu.Unlock()
12291195
// Some environments are slow when syncing and holding the lock might cause contention.
1230-
l.syncAll(files)
1196+
l.syncAll(needToSync)
12311197
}
12321198

12331199
// flushAll flushes all the logs
12341200
// l.mu is held.
1235-
func (l *loggingT) flushAll() []flushSyncWriter {
1236-
files := make([]flushSyncWriter, 0, severity.NumSeverity)
1201+
//
1202+
// The result is the number of files which need to be synced and the pointers to them.
1203+
func (l *loggingT) flushAll() fileArray {
1204+
var needToSync fileArray
1205+
12371206
// Flush from fatal down, in case there's trouble flushing.
12381207
for s := severity.FatalLog; s >= severity.InfoLog; s-- {
12391208
file := l.file[s]
1240-
if file != nil {
1241-
_ = file.Flush() // ignore error
1209+
if sb, ok := file.(*syncBuffer); ok && sb.file != nil {
1210+
_ = sb.Flush() // ignore error
1211+
needToSync.files[needToSync.num] = sb.file
1212+
needToSync.num++
12421213
}
1243-
files = append(files, file)
12441214
}
12451215
if logging.loggerOptions.flush != nil {
12461216
logging.loggerOptions.flush()
12471217
}
1248-
return files
1218+
return needToSync
1219+
}
1220+
1221+
type fileArray struct {
1222+
num int
1223+
files [severity.NumSeverity]*os.File
12491224
}
12501225

12511226
// syncAll attempts to "sync" their data to disk.
1252-
func (l *loggingT) syncAll(files []flushSyncWriter) {
1227+
func (l *loggingT) syncAll(needToSync fileArray) {
12531228
// Flush from fatal down, in case there's trouble flushing.
1254-
for _, file := range files {
1255-
if file != nil {
1256-
_ = file.Sync() // ignore error
1257-
}
1229+
for i := 0; i < needToSync.num; i++ {
1230+
_ = needToSync.files[i].Sync() // ignore error
12581231
}
12591232
}
12601233

klog_test.go

+56-9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"errors"
2222
"flag"
2323
"fmt"
24+
"io"
2425
"io/ioutil"
2526
stdLog "log"
2627
"os"
@@ -73,7 +74,7 @@ func (f *flushBuffer) Sync() error {
7374
}
7475

7576
// swap sets the log writers and returns the old array.
76-
func (l *loggingT) swap(writers [severity.NumSeverity]flushSyncWriter) (old [severity.NumSeverity]flushSyncWriter) {
77+
func (l *loggingT) swap(writers [severity.NumSeverity]io.Writer) (old [severity.NumSeverity]io.Writer) {
7778
l.mu.Lock()
7879
defer l.mu.Unlock()
7980
old = l.file
@@ -82,8 +83,8 @@ func (l *loggingT) swap(writers [severity.NumSeverity]flushSyncWriter) (old [sev
8283
}
8384

8485
// newBuffers sets the log writers to all new byte buffers and returns the old array.
85-
func (l *loggingT) newBuffers() [severity.NumSeverity]flushSyncWriter {
86-
return l.swap([severity.NumSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
86+
func (l *loggingT) newBuffers() [severity.NumSeverity]io.Writer {
87+
return l.swap([severity.NumSeverity]io.Writer{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
8788
}
8889

8990
// contents returns the specified log value as a string.
@@ -540,14 +541,17 @@ func TestOpenAppendOnStart(t *testing.T) {
540541

541542
// Logging creates the file
542543
Info(x)
543-
_, ok := logging.file[severity.InfoLog].(*syncBuffer)
544+
sb, ok := logging.file[severity.InfoLog].(*syncBuffer)
544545
if !ok {
545546
t.Fatal("info wasn't created")
546547
}
547548

548549
// ensure we wrote what we expected
549-
files := logging.flushAll()
550-
logging.syncAll(files)
550+
needToSync := logging.flushAll()
551+
if needToSync.num != 1 || needToSync.files[0] != sb.file {
552+
t.Errorf("Should have received exactly the file from severity.InfoLog for syncing, got instead: %+v", needToSync)
553+
}
554+
logging.syncAll(needToSync)
551555
b, err := ioutil.ReadFile(logging.logFile)
552556
if err != nil {
553557
t.Fatalf("unexpected error: %v", err)
@@ -811,15 +815,58 @@ func BenchmarkLogs(b *testing.B) {
811815
Severity: severity.FatalLog,
812816
}
813817
logging.logFile = testFile.Name()
814-
logging.swap([severity.NumSeverity]flushSyncWriter{nil, nil, nil, nil})
818+
logging.swap([severity.NumSeverity]io.Writer{nil, nil, nil, nil})
815819

816820
for i := 0; i < b.N; i++ {
817821
Error("error")
818822
Warning("warning")
819823
Info("info")
820824
}
821-
files := logging.flushAll()
822-
logging.syncAll(files)
825+
needToSync := logging.flushAll()
826+
sb, ok := logging.file[severity.InfoLog].(*syncBuffer)
827+
if !ok {
828+
b.Fatal("info wasn't created")
829+
}
830+
if needToSync.num != 1 || needToSync.files[0] != sb.file {
831+
b.Fatalf("Should have received exactly the file from severity.InfoLog for syncing, got instead: %+v", needToSync)
832+
}
833+
logging.syncAll(needToSync)
834+
}
835+
836+
func BenchmarkFlush(b *testing.B) {
837+
defer CaptureState().Restore()
838+
setFlags()
839+
defer logging.swap(logging.newBuffers())
840+
841+
testFile, err := ioutil.TempFile("", "test.log")
842+
if err != nil {
843+
b.Fatal("unable to create temporary file")
844+
}
845+
defer os.Remove(testFile.Name())
846+
847+
require.NoError(b, logging.verbosity.Set("0"))
848+
logging.toStderr = false
849+
logging.alsoToStderr = false
850+
logging.stderrThreshold = severityValue{
851+
Severity: severity.FatalLog,
852+
}
853+
logging.logFile = testFile.Name()
854+
logging.swap([severity.NumSeverity]io.Writer{nil, nil, nil, nil})
855+
856+
// Create output file.
857+
Info("info")
858+
needToSync := logging.flushAll()
859+
860+
if needToSync.num != 1 {
861+
b.Fatalf("expected exactly one file to sync, got: %+v", needToSync)
862+
}
863+
864+
b.ResetTimer()
865+
866+
for i := 0; i < b.N; i++ {
867+
needToSync := logging.flushAll()
868+
logging.syncAll(needToSync)
869+
}
823870
}
824871

825872
// Test the logic on checking log size limitation.

0 commit comments

Comments
 (0)