Open callendorph opened 1 year ago
Ah, the LivenessTracker
has been replaced by a simpler
finalizer system, but the c-autofree-xyz
utilities hasn't
been updated.
We'll push out a new version of the utilities. In the meantime, here is a description of how the finalizer utility works.
;Represents a finalizer that calls 'free' on a pointer
;when it executes.
public lostanza deftype MyCFinalizer <: Finalizer :
c-pointer:ptr<?>
;The 'run' method just calls 'free' on the pointer.
lostanza defmethod run (f:ref<MyCFinalizer>) -> ref<False> :
call-c clib/free(f.c-pointer)
return false
With this finalizer, you can attach it to all objects that
are declared as subtypes of Unique
.
So the QBrush
example would have to be updated like this:
;Make QBrush as subtype of Unique so that we can attach
;a finalizer to it.
public lostanza deftype QBrush <: Unique :
value:ptr<?>
...
;The QBrush constructor automatically creates a finalizer
;that deletes the pointer when the QBrush is garbage-collected.
public lostanza defn QBrush () -> ref<QBrush> :
val ptr = call-c QBrush_new()
val brush = new QBrush{ptr}
add-finalizer(new MyCFinalizer{ptr}, brush)
return brush
Another instance where the documentation is coming up short.
For the lbstanza.org
website - how is the Reference
content generated?
OK - I tried to use that in the following manner:
lostanza deftype GVectorFinalizer <: Finalizer :
value:ptr<?>
lostanza defmethod run (v:ref<GVectorFinalizer>) -> ref<False> :
fatal("running finarlizer")
call-c gsl_vector_free(v.value)
return false
public lostanza deftype GVector <: Unique :
value:ptr<?>
size:int
public lostanza defn GVector (size:ref<Int>) -> ref<GVector> :
val ptr = call-c gsl_vector_alloc(size.value)
val null = 0L as ptr<?>
if ptr == null :
throw(GSLException(gsl-ENOMEM()))
val ret = new GVector{ptr,size.value}
add-finalizer(new GVectorFinalizer{ptr}, ret)
return ret
I then created a test like this:
#use-added-syntax(tests)
defpackage gsl/GVector/tests :
import core
import gsl/GVector
defn scoped-vector () :
val uut = GVector(3)
uut[0] = 100.0
var obs = uut[0]
#EXPECT(obs == 100.0)
deftest gsl-gvector-test :
scoped-vector()
val uut = GVector(3)
uut[0] = 10.0
uut[1] = -1.0
uut[2] = 0.0
var obs = uut[0]
#EXPECT(obs == 10.0)
obs = uut[1]
#EXPECT(obs == -1.0)
obs = uut[2]
#EXPECT(obs == 0.0)
When I run this:
gsl$ ./gsl-tests.exe gsl-gvector-test
[Test 1] gsl-gvector-test
[PASS]
Tests Finished: 1/1 tests passed. 0 tests skipped. 0 tests failed.
Longest Running Tests:
[PASS] gsl-gvector-test (792 us)
I'm using stanza version 0.17.33 in WSL.
I expect the fatal
to trigger in the finalizer's run
method but it never does.
Is there some aspect of the unit test framework that I'm misunderstanding ? Can you replicate this behavior?
I see the finalizer used in the Process
object in core.stanza
so it seems like this is in use.
Hi Carl,
As far as I can tell, your code is correct, and the testing framework is also doing what it should.
Are you expecting the fatal
to trigger at a very specific moment?
The finalizer is executed when the GVector
is eventually garbage-collected, but the test is probably too short to trigger the GC.
If you execute a loop afterwards that does nothing except allocate a bunch of memory:
for i in 0 to 10000 do :
Array<?>(1000)
This will likely trigger the GC, and cause your finalizer to run.
Are you expecting the fatal to trigger at a very specific moment? The finalizer is executed when the GVector is eventually garbage-collected, but the test is probably too short to trigger the GC.
OK - I probably don't understand the GC very well. I was expecting the GC to collect the GVector object at two places:
scoped-vector
function where uut
comes into and out of scope.deftest
body when the other uut
goes out of a scope. When does the GC run ? Is there a heuristic for the GC ?
Oh I see.
Stanza works off of a different mental model for the GC, that is conceptually closer to Ruby/Java than to C++ destructors. The mental model is that Stanza starts up with a small heap of about 4MB, and begins executing your program. All allocations are taken from this heap, and so the heap gradually fills up until it is full. When it is full, it triggers a GC which sweeps through the 4MB and deletes the dead objects, and continues executing your program afterwards. We play tricks in the backend to make sure that the sweeping and deleting can be done very quickly.
If the GC wasn’t able to clear up much space, then the heap is enlarged, and your program is resumed with a larger heap.
For something like your GVector
example, your usage of the finalizer will work well. But it does mean that you shouldn’t rely upon any specific timing of when the GC runs.
OK - that is very helpful - thanks!
For something like your GVector example, your usage of the finalizer will work well.
I agree that the finalizer
implementation in this case does seem appropriate for the GVector object.
Hi,
In your documentation for the Tetris example, you talk about
c-autofree-marker
and theLivenessMarker
object.In the stanza code here:
https://github.com/StanzaOrg/lbstanza/blob/229ee4e013e0f411742390f31bf97af159ddc1f1/core/core.stanza#L9771
This implementation is commented out. This causes the tetris example to not build on relatively new versions of stanza because it can't resolve
LivenessMarker
orc-autofree-marker
.What is the status of this code? I attempted to follow what was going on in the git history. From what I can tell - that code gets commented out in 79805853dd4.
It seems to me like this is a pretty critical feature for creating sane wrappers around moderately complicated C libraries.
Is there an alternate or work around for this functionality?