On Mon, Sep 5, 2016 at 9:08 PM, Ben Coman <[email protected]> wrote:
> Is it a **requirement** that FFI calls have a method all to themselves
> like a primitive call?
>
> I have this definition...
>
> FFIExternalStructure subclass: #CXString
>
> CXString class>>fieldsDesc
>     ^ #(
>     void *data;
>     uint private_flags;
>     )
>
>
> The instance returned by this...
>     Xxxx>>getClangVersion
>         ^ self ffiCall: #( CXString clang_getClangVersion () ) module: 
> Libclang
>
> has private_flags=1 indicating the library allocated external memory
> for *data and to release this I need to call  clang_disposeString(
> CXString string).  However this doesn't guard against being called
> twice and double-free()'ing CXString crashing the VM, so I defined the
> following...
>
> CXString>>dispose
>    self inform: 'debug_dispose1'.
>    self private_flags = 1
>        ifTrue:
>             [ self inform: 'debug_dispose2'.
>               self private_flags: 0.
>                self ffiCall: #( void clang_disposeString ( CXString
> self ) ) module: Libclang
>             ]
>        ifFalse:
>            [  self inform: 'debug_dispose3'.
>               Error signal: 'Cannot dispose twice' ].
>
> However something strange, dispose will only execute once.  For example for...
>
> s := Libclang getClangVersion.
> s dispose.   "==>debug_dispose1, debug_dispose2"
> s dispose.   "==>nothing at all"
>
> I would expect the second call to dispose
> to result it "==>debug_dispose1, debug_dispose3"
> but it does nothing at all.  Debugging into "S dispose" displays the
> source of dispose and then returns without executing anything.  The
> image seems fine and displays the updated zero in private_flags -- at
> least for a few minutes, then it might crash indicating the
> clang_disposeString() has been called twice.
>
> However it works if I separate out the FFI call to a separate method...
>
> CXString>>unsafeDispose
>     self ffiCall: #( void clang_disposeString ( CXString self ) )
> module: Libclang
>
> CXString>>dispose
>    self inform: 'dispose1'.
>    self private_flags = 1
>        ifTrue:
>             [ self inform: 'dispose2'.
>               self private_flags: 0.
>               self unsafeDispose.
>             ]
>        ifFalse:
>            [  self inform: 'dispose3'.
>               Error signal: 'Cannot dispose twice' ].
>
> s := Libclang getClangVersion.
> s dispose.   "==>debug_dispose1, debug_dispose2"
> s dispose.   "==>debug_dispose1, debug_dispose3"
> s dispose.   "==>debug_dispose1, debug_dispose3"
> s dispose.   "==>debug_dispose1, debug_dispose3"
>
>
> Can someone hazard a guess what is behind this behaviour?  I would
> prefer not to require the unguarded #unsafeDispose.
>
> cheers -ben


I'm also trying to understand the FFI memory footprint.
A freshly started image uses this memory (kb)...
    VIRT    RES    SHR
118132 108644   5920

Creation and GC of the FFIExternalStructure is no problem...
   all := OrderedCollection new.
   10 000 000 timesRepeat: [ all add: CXString new. ].
   all := nil.
   Smalltalk garbageCollect.
        VIRT     RES     SHR
 122,268 115,244   5,788

And performing the FFI calls is not too bad (particularly that
repeating this doesn't grow memory further)...
    10 000 000 timesRepeat: [ Libclang getClangVersion unsafeDispose ].
    Smalltalk garbageCollect.
        VIRT    RES    SHR
 170,404 123,252  12,084

But combining those two...
    all := OrderedCollection new.
    1000000 timesRepeat: [ all add: Libclang getClangVersion ].
    all do: [  :s | s unsafeDispose. ].
    all := nil.
    Smalltalk garbageCollect.
    CXString allInstances size "==> 0".
increases memory considerably...
         VIRT          RES        SHR
1,029,836    982,336    11,740

But again, repeating that a dozen times doesn't grow memory further.

Save/quiting the image and reopening it returns to original memory usage...
        VIRT        RES       SHR
118,132     108,620     5,896
.

So I'm curious what might be holding onto the memory.

cheers -ben


P.S. a bit more background info on the library...

extern "C" {
  CXString clang_getClangVersion() {
    return cxstring::createDup(getClangFullVersion());
  }
} // 
https://github.com/llvm-mirror/clang/blob/google/stable/tools/libclang/CIndex.cpp


CXString createDup(const char *String) {
  if (!String)
    return createNull();
  if (String[0] == '\0')
    return createEmpty();
  CXString Str;
  Str.data = strdup(String);
  Str.private_flags = CXS_Malloc;
  return Str;
}


void clang_disposeString(CXString string) {
  switch ((CXStringFlag) string.private_flags) {
    case CXS_Unmanaged:
       break;
    case CXS_Malloc:
      if (string.data)
        free(const_cast<void *>(string.data));
        break;
     case CXS_StringBuf:
        static_cast<cxstring::CXStringBuf *>(
        const_cast<void *>(string.data))->dispose();
        break;
    }
}

Reply via email to