ethlog: fix concurrency

Rather than spawning a new goroutine for each message,
run each log system in a dedicated goroutine.

Ensure that logging is still asynchronous by using a per-system buffer
(currently 500 messages). If it overflows all logging will hang,
but that's better than spawning indefinitely many goroutines.
poc8
Felix Lange 10 years ago
parent c090a77f1c
commit fd9b03a431
  1. 98
      ethlog/loggers.go

@ -47,75 +47,97 @@ const (
) )
var ( var (
mutex sync.RWMutex // protects logSystems logMessageC = make(chan message)
logSystems []LogSystem addSystemC = make(chan LogSystem)
flushC = make(chan chan struct{})
logMessages = make(chan message) resetC = make(chan chan struct{})
drainWaitReq = make(chan chan struct{})
) )
func init() { func init() {
go dispatchLoop() go dispatchLoop()
} }
// each system can buffer this many messages before
// blocking incoming log messages.
const sysBufferSize = 500
func dispatchLoop() { func dispatchLoop() {
var drainWait []chan struct{} var (
dispatchDone := make(chan struct{}) systems []LogSystem
pending := 0 systemIn []chan message
systemWG sync.WaitGroup
)
bootSystem := func(sys LogSystem) {
in := make(chan message, sysBufferSize)
systemIn = append(systemIn, in)
systemWG.Add(1)
go sysLoop(sys, in, &systemWG)
}
for { for {
select { select {
case msg := <-logMessages: case msg := <-logMessageC:
go dispatch(msg, dispatchDone) for _, c := range systemIn {
pending++ c <- msg
case waiter := <-drainWaitReq: }
if pending == 0 {
close(waiter) case sys := <-addSystemC:
} else { systems = append(systems, sys)
drainWait = append(drainWait, waiter) bootSystem(sys)
case waiter := <-resetC:
// reset means terminate all systems
for _, c := range systemIn {
close(c)
}
systems = nil
systemIn = nil
systemWG.Wait()
close(waiter)
case waiter := <-flushC:
// flush means reboot all systems
for _, c := range systemIn {
close(c)
} }
case <-dispatchDone: systemIn = nil
pending-- systemWG.Wait()
if pending == 0 { for _, sys := range systems {
for _, c := range drainWait { bootSystem(sys)
close(c)
}
drainWait = nil
} }
close(waiter)
} }
} }
} }
func dispatch(msg message, done chan<- struct{}) { func sysLoop(sys LogSystem, in <-chan message, wg *sync.WaitGroup) {
mutex.RLock() for msg := range in {
for _, sys := range logSystems {
if sys.GetLogLevel() >= msg.level { if sys.GetLogLevel() >= msg.level {
sys.LogPrint(msg.level, msg.msg) sys.LogPrint(msg.level, msg.msg)
} }
} }
mutex.RUnlock() wg.Done()
done <- struct{}{}
} }
// Reset removes all active log systems. // Reset removes all active log systems.
// It blocks until all current messages have been delivered.
func Reset() { func Reset() {
mutex.Lock() waiter := make(chan struct{})
logSystems = nil resetC <- waiter
mutex.Unlock() <-waiter
} }
// Flush waits until all current log messages have been dispatched to // Flush waits until all current log messages have been dispatched to
// the active log systems. // the active log systems.
func Flush() { func Flush() {
waiter := make(chan struct{}) waiter := make(chan struct{})
drainWaitReq <- waiter flushC <- waiter
<-waiter <-waiter
} }
// AddLogSystem starts printing messages to the given LogSystem. // AddLogSystem starts printing messages to the given LogSystem.
func AddLogSystem(logSystem LogSystem) { func AddLogSystem(sys LogSystem) {
mutex.Lock() addSystemC <- sys
logSystems = append(logSystems, logSystem)
mutex.Unlock()
} }
// A Logger prints messages prefixed by a given tag. It provides named // A Logger prints messages prefixed by a given tag. It provides named
@ -130,11 +152,11 @@ func NewLogger(tag string) *Logger {
} }
func (logger *Logger) sendln(level LogLevel, v ...interface{}) { func (logger *Logger) sendln(level LogLevel, v ...interface{}) {
logMessages <- message{level, logger.tag + fmt.Sprintln(v...)} logMessageC <- message{level, logger.tag + fmt.Sprintln(v...)}
} }
func (logger *Logger) sendf(level LogLevel, format string, v ...interface{}) { func (logger *Logger) sendf(level LogLevel, format string, v ...interface{}) {
logMessages <- message{level, logger.tag + fmt.Sprintf(format, v...)} logMessageC <- message{level, logger.tag + fmt.Sprintf(format, v...)}
} }
// Errorln writes a message with ErrorLevel. // Errorln writes a message with ErrorLevel.

Loading…
Cancel
Save