Skip to content

Instantly share code, notes, and snippets.

@hraban
Created November 5, 2013 22:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hraban/7327813 to your computer and use it in GitHub Desktop.
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
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