Skip to content

Commit 9cf13b7

Browse files
cezarsamcuadros
authored andcommitted
Fix bug in datagram message due to shared buffer (#34)
This bug was introduced by me in 8487663 and I'm sorry for that. The problem is that as the DatagramMessage channel is buffered we cannot simply use the same buffer for reading all incoming datagrams, because reading will change the []byte for all messages still buffered in the channel. I added some tests that fail on the previous version in the hope of avoiding this same mistake in the future. The solution was using a sync.Pool instance to acquire a buffer and putting it back on the pool only when it was received and processed. This solution has the same advantages of reusing the buffer without the risk of changing a buffer still in use. And for some reason it seems even faster than simply using the same buffer: benchmark old ns/op new ns/op delta BenchmarkDatagramNoFormatting-4 2693 2398 -10.95% benchmark old MB/s new MB/s speedup BenchmarkDatagramNoFormatting-4 17.45 19.59 1.12x benchmark old allocs new allocs delta BenchmarkDatagramNoFormatting-4 4 5 +25.00% benchmark old bytes new bytes delta BenchmarkDatagramNoFormatting-4 368 487 +32.34%
1 parent e2fcef3 commit 9cf13b7

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

server.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ type Server struct {
3939
lastError error
4040
readTimeoutMilliseconds int64
4141
tlsPeerNameFunc TlsPeerNameFunc
42+
datagramPool sync.Pool
4243
}
4344

4445
//NewServer returns a new Server
4546
func NewServer() *Server {
46-
return &Server{tlsPeerNameFunc: defaultTlsPeerName}
47+
return &Server{tlsPeerNameFunc: defaultTlsPeerName, datagramPool: sync.Pool{
48+
New: func() interface{} {
49+
return make([]byte, 65536)
50+
},
51+
}}
4752
}
4853

4954
//Sets the syslog format (RFC3164 or RFC5424 or RFC6587)
@@ -319,8 +324,8 @@ func (s *Server) goReceiveDatagrams(packetconn net.PacketConn) {
319324
s.wait.Add(1)
320325
go func() {
321326
defer s.wait.Done()
322-
buf := make([]byte, 65536)
323327
for {
328+
buf := s.datagramPool.Get().([]byte)
324329
n, addr, err := packetconn.ReadFrom(buf)
325330
if err == nil {
326331
// Ignore trailing control characters and NULs
@@ -366,6 +371,7 @@ func (s *Server) goParseDatagrams() {
366371
} else {
367372
s.parser(msg.message, msg.client, "")
368373
}
374+
s.datagramPool.Put(msg.message[:cap(msg.message)])
369375
}
370376
}
371377
}()

server_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,58 @@ func (s *ServerSuite) TestUDPAutomatic5424Plus6587OctetCount(c *C) {
271271
c.Check(handler.LastMessageLength, Equals, int64(len(exampleRFC5424Syslog)))
272272
c.Check(handler.LastError, IsNil)
273273
}
274+
275+
type handlerSlow struct {
276+
*handlerCounter
277+
contents []string
278+
}
279+
280+
func (s *handlerSlow) Handle(logParts format.LogParts, msgLen int64, err error) {
281+
if len(s.contents) == 0 {
282+
time.Sleep(time.Second)
283+
}
284+
s.contents = append(s.contents, logParts["content"].(string))
285+
s.handlerCounter.Handle(logParts, msgLen, err)
286+
}
287+
288+
func (s *ServerSuite) TestUDPRace(c *C) {
289+
handler := &handlerSlow{handlerCounter: &handlerCounter{expected: 3, done: make(chan struct{})}}
290+
server := NewServer()
291+
server.SetFormat(Automatic)
292+
server.SetHandler(handler)
293+
server.SetTimeout(10)
294+
server.ListenUDP("127.0.0.1:0")
295+
server.Boot()
296+
conn, err := net.Dial("udp", server.connections[0].LocalAddr().String())
297+
c.Assert(err, IsNil)
298+
_, err = conn.Write([]byte(exampleSyslog + "1"))
299+
c.Assert(err, IsNil)
300+
_, err = conn.Write([]byte(exampleSyslog + "2"))
301+
c.Assert(err, IsNil)
302+
_, err = conn.Write([]byte(exampleSyslog + "3"))
303+
c.Assert(err, IsNil)
304+
conn.Close()
305+
<-handler.done
306+
c.Check(handler.contents, DeepEquals, []string{"content1", "content2", "content3"})
307+
}
308+
309+
func (s *ServerSuite) TestTCPRace(c *C) {
310+
handler := &handlerSlow{handlerCounter: &handlerCounter{expected: 3, done: make(chan struct{})}}
311+
server := NewServer()
312+
server.SetFormat(Automatic)
313+
server.SetHandler(handler)
314+
server.SetTimeout(10)
315+
server.ListenTCP("127.0.0.1:0")
316+
server.Boot()
317+
conn, err := net.Dial("tcp", server.listeners[0].Addr().String())
318+
c.Assert(err, IsNil)
319+
_, err = conn.Write([]byte(exampleSyslog + "1\n"))
320+
c.Assert(err, IsNil)
321+
_, err = conn.Write([]byte(exampleSyslog + "2\n"))
322+
c.Assert(err, IsNil)
323+
_, err = conn.Write([]byte(exampleSyslog + "3\n"))
324+
c.Assert(err, IsNil)
325+
conn.Close()
326+
<-handler.done
327+
c.Check(handler.contents, DeepEquals, []string{"content1", "content2", "content3"})
328+
}

0 commit comments

Comments
 (0)