idris-lang / Idris2

A purely functional programming language with first class types
https://idris-lang.org/
Other
2.5k stars 375 forks source link

Is it safe to pass a GC'ed pointer to C? #3290

Open joelberkeley opened 4 months ago

joelberkeley commented 4 months ago

In the following freehand code I'm concerned that Idris can garbage collect the Foo pointer while it's still in use. I have had mixed experience with using this pattern. Sometimes I get memory errors, sometimes not. But when I did debug it it seemed to fall over after the first time the Idris GC ran.

struct Foo { int n; };

Foo* mkfoo () {
  return new Foo{0};
}

int slow (Foo* foo) {
  usleep(9999999999);  // Idris runs GC here
  return foo->n;       // foo no longer allocated
}
data Foo = MkFoo GCAnyPtr

%foreign "mkfoo"
mkfoo : PrimIO AnyPtr

foo : IO Foo
foo = do
  foo <- primIO mkfoo
  foo <- onCollectAny foo free
  pure (MkFoo foo)

%foreign "slow"
prim__slow : GCAnyPtr -> Int

slow : Foo -> Int
slow (MkFoo foo) = prim__slow foo

main : IO ()
main = do
  foo <- mkfoo
  printLn (slow foo)  

If this is a bug, perhaps it's OK if it remains as a limitation of finalisers onCollect[Any], if it's documented VERY LOUDLY cos it defeats the whole purpose of the function when used as above, i.e. safe automatic memory management. That said, I can't see when you'd use it in any other way. Would you ever have a pointer that you don't pass back to C? In what scenario are finalisers safe?