Created
November 5, 2013 22:53
-
-
Save hraban/7327813 to your computer and use it in GitHub Desktop.
poc to clean up a mainloop goroutine when the "public pointer" runs out and is garbage collected
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"runtime" | |
"time" | |
"unsafe" | |
) | |
type foo struct { | |
c chan int | |
// a value is sent down this channel by the main loop once it's done with f | |
// (i.e. after this no more access to any of f by the main loop, meaning | |
// the memory it occupied can be reclaimed) | |
loopDone chan int | |
} | |
func ptr2f(u uintptr) *foo { | |
p := unsafe.Pointer(u ^ 0xbeefbabe) | |
f := (*foo)(p) | |
return f | |
} | |
func ptr2chan(u uintptr) <-chan int { | |
return ptr2f(u).c | |
} | |
// returns true if the body should be run again, false to stop looping. | |
// accepts a uintptr to allow closures of this func without keeping refcount to | |
// foo up | |
func loopbody(u uintptr) (repeat bool) { | |
c := ptr2chan(u) | |
i := <-c | |
fmt.Println("F's loop got", i) | |
if i == 666 { | |
f := ptr2f(u) | |
f.loopDone <- 80085 | |
// do not touch f after this! | |
return false | |
} | |
return true | |
} | |
// call loopbody with u until it returns false | |
// use this to allow pointers created in loop body to go out of scope | |
// between runs. | |
func loop(u uintptr) { | |
for loopbody(u) { | |
} | |
fmt.Println("F's loop exiting") | |
} | |
func (f *foo) init() { | |
f.c = make(chan int) | |
f.loopDone = make(chan int) | |
u := uintptr(unsafe.Pointer(f)) ^ 0xbeefbabe | |
go loop(u) | |
fmt.Printf("Created f at %p: %v\n", f, f) | |
// f goes out of scope here | |
} | |
func newf() *foo { | |
f := &foo{} | |
f.init() | |
runtime.SetFinalizer(f, func(f *foo) { | |
fmt.Printf("running finalizer for %p\n", f) | |
f.c <- 666 | |
<-f.loopDone | |
fmt.Printf("%p finalized\n", f) | |
}) | |
return f | |
} | |
// creates a foo object, uses it, lets refcount run down to zero | |
func doit() { | |
f := newf() | |
f.c <- 123 | |
// keep it around for a while | |
//time.AfterFunc(time.Second, func() { f.c <- 45 }) | |
} | |
func main() { | |
n := runtime.NumGoroutine() | |
doit() | |
fmt.Println("cleanup") | |
// tickle the gc for some 5 seconds | |
start := time.Now() | |
for time.Since(start) < 5*time.Second { | |
runtime.GC() | |
runtime.Gosched() | |
// and also some random object creation just to mess with the heap | |
go func(f *foo) { fmt.Sprint(f) }(&foo{}) | |
} | |
n2 := runtime.NumGoroutine() | |
leak := n2 - n | |
if leak != 0 { | |
fmt.Println("leaking goroutines:", leak) | |
panic("dumping callstacks") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment