From b042c5e48dcf4b2c19b6feea14e7f4b5eaca7ddf Mon Sep 17 00:00:00 2001 From: YACOVM Date: Fri, 30 Sep 2016 21:15:01 +0300 Subject: [PATCH] Thread safety fix in logging and SetLevel https://github.com/op/go-logging/issues/104 When a logging is called concurrently with SetLevel, a panic occurs: fatal error: concurrent map read and map write goroutine 20 [running]: runtime.throw(0x5f7cc0, 0x21) /home/yacovm/OBC/go/src/runtime/panic.go:530 +0x90 fp=0xc820038d58 sp=0xc820038d40 runtime.mapaccess2_faststr(0x551020, 0xc820078780, 0x5c9cc8, 0x4, 0x10, 0x551820) /home/yacovm/OBC/go/src/runtime/hashmap_fast.go:307 +0x5b fp=0xc820038db8 sp=0xc820038d58 _/home/yacovm/go-logging/go-logging.(*moduleLeveled).IsEnabledFor(0xc8200981e0, 0x4, 0x5c9cc8, 0x4, 0x0) /home/yacovm/go-logging/go-logging/level.go:110 +0x62 fp=0xc820038e18 sp=0xc820038db8 _/home/yacovm/go-logging/go-logging.(*Logger).IsEnabledFor(0xc8200788a0, 0x4, 0x0) /home/yacovm/go-logging/go-logging/logger.go:140 +0x4d fp=0xc820038e48 sp=0xc820038e18 _/home/yacovm/go-logging/go-logging.(*Logger).log(0xc8200788a0, 0x4, 0x0, 0xc8200d9b30, 0x1, 0x1) /home/yacovm/go-logging/go-logging/logger.go:144 +0x2f fp=0xc820038eb8 sp=0xc820038e48 _/home/yacovm/go-logging/go-logging.(*Logger).Info(0xc8200788a0, 0xc8200d9b30, 0x1, 0x1) /home/yacovm/go-logging/go-logging/logger.go:239 +0x51 fp=0xc820038ef0 sp=0xc820038eb8 _/home/yacovm/go-logging/go-logging.TestConcurrency(0xc8200a0120) /home/yacovm/go-logging/go-logging/logger_test.go:73 +0x11f fp=0xc820038f68 sp=0xc820038ef0 testing.tRunner(0xc8200a0120, 0x6b8468) I added a RWLock to protect the map and added a regression test that many times fails without the locks --- level.go | 5 +++++ logger_test.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/level.go b/level.go index 98dd191..5123f8e 100644 --- a/level.go +++ b/level.go @@ -66,6 +66,7 @@ type LeveledBackend interface { } type moduleLeveled struct { + sync.RWMutex levels map[string]Level backend Backend formatter Formatter @@ -88,6 +89,8 @@ func AddModuleLevel(backend Backend) LeveledBackend { // GetLevel returns the log level for the given module. func (l *moduleLeveled) GetLevel(module string) Level { + l.RLock() + defer l.RUnlock() level, exists := l.levels[module] if exists == false { level, exists = l.levels[""] @@ -101,6 +104,8 @@ func (l *moduleLeveled) GetLevel(module string) Level { // SetLevel sets the log level for the given module. func (l *moduleLeveled) SetLevel(level Level, module string) { + l.Lock() + defer l.Unlock() l.levels[module] = level } diff --git a/logger_test.go b/logger_test.go index b9f7fe7..470e39b 100644 --- a/logger_test.go +++ b/logger_test.go @@ -60,3 +60,17 @@ func TestPrivateBackend(t *testing.T) { t.Error("logged to defaultBackend:", MemoryRecordN(privateBackend, 0)) } } + +func TestConcurrency(t *testing.T) { + logger := MustGetLogger("test") + go func() { + for i := 0; i < 1000; i++ { + SetLevel(Level(DEBUG), "test") + } + }() + + for i := 0; i < 1000; i++ { + logger.Info("") + } +} +