- dns query log: robust file flushing mechanism

Before this patch we could exit the process without waiting for
 file writing task to complete.
As a result a file could become corrupted or a large chunk of data
 could be missing.

Now the main thread either waits until file writing task completes
 or it writes log buffer to file itself.
This commit is contained in:
Simon Zolin 2019-05-15 13:11:36 +03:00
parent 0f28a989e9
commit d5f6dd1a46
3 changed files with 21 additions and 13 deletions

View File

@ -236,7 +236,7 @@ func (s *Server) stopInternal() error {
} }
// flush remainder to file // flush remainder to file
return s.queryLog.flushLogBuffer() return s.queryLog.flushLogBuffer(true)
} }
// IsRunning returns true if the DNS server is running // IsRunning returns true if the DNS server is running

View File

@ -30,6 +30,8 @@ type queryLog struct {
logBufferLock sync.RWMutex logBufferLock sync.RWMutex
logBuffer []*logEntry logBuffer []*logEntry
fileFlushLock sync.Mutex // synchronize a file-flushing goroutine and main thread
flushPending bool // don't start another goroutine while the previous one is still running
queryLogCache []*logEntry queryLogCache []*logEntry
queryLogLock sync.RWMutex queryLogLock sync.RWMutex
@ -91,13 +93,15 @@ func (l *queryLog) logRequest(question *dns.Msg, answer *dns.Msg, result *dnsfil
IP: ip, IP: ip,
Upstream: upstream, Upstream: upstream,
} }
var flushBuffer []*logEntry
l.logBufferLock.Lock() l.logBufferLock.Lock()
l.logBuffer = append(l.logBuffer, &entry) l.logBuffer = append(l.logBuffer, &entry)
if len(l.logBuffer) >= logBufferCap { needFlush := false
flushBuffer = l.logBuffer if !l.flushPending {
l.logBuffer = nil needFlush = len(l.logBuffer) >= logBufferCap
if needFlush {
l.flushPending = true
}
} }
l.logBufferLock.Unlock() l.logBufferLock.Unlock()
l.queryLogLock.Lock() l.queryLogLock.Lock()
@ -116,15 +120,10 @@ func (l *queryLog) logRequest(question *dns.Msg, answer *dns.Msg, result *dnsfil
} }
// if buffer needs to be flushed to disk, do it now // if buffer needs to be flushed to disk, do it now
if len(flushBuffer) > 0 { if needFlush {
// write to file // write to file
// do it in separate goroutine -- we are stalling DNS response this whole time // do it in separate goroutine -- we are stalling DNS response this whole time
go func() { go l.flushLogBuffer(false)
err := l.flushToFile(flushBuffer)
if err != nil {
log.Printf("Failed to flush the query log: %s", err)
}
}()
} }
return &entry return &entry

View File

@ -20,11 +20,20 @@ var (
const enableGzip = false const enableGzip = false
// flushLogBuffer flushes the current buffer to file and resets the current buffer // flushLogBuffer flushes the current buffer to file and resets the current buffer
func (l *queryLog) flushLogBuffer() error { func (l *queryLog) flushLogBuffer(fullFlush bool) error {
l.fileFlushLock.Lock()
defer l.fileFlushLock.Unlock()
// flush remainder to file // flush remainder to file
l.logBufferLock.Lock() l.logBufferLock.Lock()
needFlush := len(l.logBuffer) >= logBufferCap
if !needFlush && !fullFlush {
l.logBufferLock.Unlock()
return nil
}
flushBuffer := l.logBuffer flushBuffer := l.logBuffer
l.logBuffer = nil l.logBuffer = nil
l.flushPending = false
l.logBufferLock.Unlock() l.logBufferLock.Unlock()
err := l.flushToFile(flushBuffer) err := l.flushToFile(flushBuffer)
if err != nil { if err != nil {