5

When using CGo to interface C code with Go, if I keep a reference to a Go variable on the C side, do I run the risk of that object being freed by the garbage collector or will the GC see the pointer in the variables managed by the C side?

To illustrate what I'm asking, consider the following sample program:

Go code:

package main

/*
typedef struct _Foo Foo;
Foo *foo_new(void);
void foo_send(Foo *foo, int x);
int foo_recv(Foo *foo);
*/
import "C"

//export makeChannel
func makeChannel() chan int {
    return make(chan int, 1)
}

//export sendInt
func sendInt(ch chan int, x int) {
    ch <- x
}

//export recvInt
func recvInt(ch chan int) int {
    return <-ch
}

func main() {
    foo := C.foo_new()
    C.foo_send(foo, 42)
    println(C.foo_recv(foo))
}

C code:

#include <stdlib.h>
#include "_cgo_export.h"

struct _Foo {
    GoChan ch;
};

Foo *foo_new(void) {
    Foo *foo = malloc(sizeof(Foo));
    foo->ch = makeChannel();
    return foo;
}

void foo_send(Foo *foo, int x) {
    sendInt(foo->ch, x);
}

int foo_recv(Foo *foo) {
    return recvInt(foo->ch);
}

Do I run the risk of foo->ch being freed by the garbage collector between the foo_new and foo_send calls? If so, is there a way to pin the Go variable from the C side to prevent it from being freed while I hold a reference to it?

James Henstridge
  • 42,244
  • 6
  • 132
  • 114
  • Seems like there isn't a way to prevent Go's garbage collector other than turning it off. http://blog.golang.org/c-go-cgo says `Memory allocations made by C code are not known to Go's memory manager.` so I would assume that you need to copy the Go value to a C variable if you want to keep a reference to it. – Brenden Dec 03 '13 at 22:56
  • That part of the blog article just seems to be saying that memory allocated in C won't be freed by Go's GC, which is not what I'm asking about: rather than asking about the lifetime of the `Foo` structure, I'm wondering what the lifetime of the channel I've stored a pointer to. Copying the variable isn't an option for things like channels (for obvious reasons). – James Henstridge Dec 03 '13 at 23:07

1 Answers1

3

According to the gmp CGo example :

Garbage collection is the big problem. It is fine for the Go world to have pointers into the C world and to free those pointers when they are no longer needed. To help, the Go code can define Go objects holding the C pointers and use runtime.SetFinalizer on those Go objects.

It is much more difficult for the C world to have pointers into the Go world, because the Go garbage collector is unaware of the memory allocated by C. The most important consideration is not to constrain future implementations, so the rule is that Go code can hand a Go pointer to C code but must separately arrange for Go to hang on to a reference to the pointer until C is done with it.

So I'm not sure if you can pin the variable from the C side, but you may be able to control the garbage collection of the variable from the Go side by using the runtime.SetFinalizer function.

Hope that helps.

Intermernet
  • 18,604
  • 4
  • 49
  • 61
  • 2
    Or: live Go data must always be referenced from the Go heap, because that's all that the GC will scan. Sometimes, to achieve that, your C can call a Go function to do its allocations of reference-holding types. (After all, as long as all your references are in the Go heap, you're fine.) Other times the reference comes from malloc-allocated memory, so you have to write malloc/free wrappers or something to artificially create a references to the Go object from elsewhere in Go-land. For example, those wrappers might be Go functions that add/remove entries in a `map[uintptr]interface{}`. – twotwotwo Dec 04 '13 at 07:06
  • 2
    Thanks. I've settled for managing a map of the channels on the Go side: adding to the map when I want to store them on the C side, and removing them from the map when I want to release them on the C side. – James Henstridge Dec 04 '13 at 10:18
  • How do you garbage collect the variable manually after turning off GC on a variable? – Drew Dec 01 '14 at 20:29