Up to Main Index Up to Journal for May, 2012 JOURNAL FOR TUESDAY 1ST MAY, 2012 ______________________________________________________________________________ SUBJECT: Oh bother ... don't think it's my memory leak :( DATE: Tue May 1 22:14:11 BST 2012 So I'm still chasing memory leaks. Going through my code adding lots of debugging, making the code a mess *sigh* Now I've come up with a simple test case: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 - - - - package main import ( "net" "os" "runtime" "runtime/pprof" ) func main() { ln, err := net.Listen("tcp", "localhost:4001") if err != nil { println("Listen Error") return } m := new(runtime.MemStats) oa, oho := uint64(0), uint64(0) oi := 0 for i := 0; i < 10000; i++ { if conn, err := ln.Accept(); err != nil { println("Accept error") return } else { conn.Close() runtime.GC() runtime.ReadMemStats(m) if oa != m.Alloc || oho != m.HeapObjects { // Print #connections, #connections since last Alloc/HeapObjects // change, current Alloc and HeapObjects println("Connected", i, (i - oi), m.Alloc, m.HeapObjects) oi, oa, oho = i, m.Alloc, m.HeapObjects } } } f, err := os.Create("memprof") pprof.WriteHeapProfile(f) f.Close() } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->8 - - - - It just accepts a connection and closes it. I've been testing it with 10001 connections using: seq 1 10001 | xargs -iz telnet 127.0.0.1 4001 A sample run displays: Connected 0 0 344400 337 Connected 1 1 345072 339 Connected 3 2 345744 341 Connected 8 5 346416 343 Connected 580 572 417776 346 Connected 1392 812 417968 347 Connected 2142 750 418160 348 Connected 2768 626 418336 349 Connected 4259 1491 418512 350 Connected 4326 67 418688 351 Connected 4927 601 418880 352 Connected 6261 1334 419056 353 Connected 6990 729 419232 354 Connected 7113 123 419408 355 Connected 7822 709 419584 356 Connected 7897 75 419792 357 Connected 8460 563 419968 358 In the 3rd column you can see the allocated memory rising and in the 4th column you can see the number of HeapObjects rising. Using pprof to look at the memory profile the program writes out at the end we can see where the object allocations are: $pprof --inuse_objects tester memprof Adjusting heap profiles for 1-in-524288 sampling rate Welcome to pprof! For help, type 'help'. (pprof) top Total: 10922 objects 10922 100.0% 100.0% 10922 100.0% os.NewFile 0 0.0% 100.0% 10922 100.0% main.main 0 0.0% 100.0% 10922 100.0% net.(*TCPListener).Accept 0 0.0% 100.0% 10922 100.0% net.(*TCPListener).AcceptTCP 0 0.0% 100.0% 10922 100.0% net.(*netFD).accept 0 0.0% 100.0% 10922 100.0% net.(*netFD).setAddr 0 0.0% 100.0% 10922 100.0% runtime.main 0 0.0% 100.0% 10922 100.0% schedunlock Ok, so all the allocations are in os.NewFile, lets look at that: (pprof) list os.NewFile Total: 10922 objects ROUTINE ====================== os.NewFile in /usr/local/go/src/pkg/os/file… 10922 10922 Total objects (flat / cumulative) . . 34: } . . 35: return uintptr(f.fd) . . 36: } . . 37: . . 38: // NewFile returns a new File with the given file descr… --- . . 39: func NewFile(fd uintptr, name string) *File { . . 40: fdi := int(fd) . . 41: if fdi < 0 { . . 42: return nil . . 43: } 10922 10922 44: f := &File{&file{fd: fdi, name: name}} . . 45: runtime.SetFinalizer(f.file, (*file).close) . . 46: return f --- . . 47: } . . 48: . . 49: // Auxiliary information if the File describes a direct… . . 50: type dirInfo struct { . . 51: buf []byte // buffer for directory I/O (pprof) quit Looks like the finalizers are not running to release the File objects :( I've tried adding runtime.Gosched(), runtime.GC() and time.Sleep() all over the place, even changing GOMAXPROCS > 1, but nothing seems to work ... might be time to file a bug? -- Diddymus Up to Main Index Up to Journal for May, 2012