Destructors

58 views
Skip to first unread message

Toby Gifford

unread,
Jul 27, 2015, 1:27:59 AM7/27/15
to extemp...@googlegroups.com
A question on extempore-style; how does one deal with free-ing up resources, equivalent to a C++ destructor, or a Java finaliser etc.

For example, suppose my dsp:DSP routine acquires some resources beyond just zone-allocated memory–say i'm using some external C library that malloc's something, or opens a database cursor etc. If I redefine dsp:DSP then, as I understand it, the zone associated with the closure is deallocated, and everything else that was zalloc'd just hangs around until the default (or otherwise created) zone is manually blown away. But what about these other resources? Is there a way to provide a hook for a cleanup routine?

Or, if i'm acquiring resources in a dsp closure, do I just need to make sure I manually run a cleanup routine before redefining?

Andrew Sorensen

unread,
Jul 28, 2015, 6:01:42 AM7/28/15
to extemp...@googlegroups.com
Hi Toby,

This is obviously an important issue, and as alluded to below, a non-solved issue, in the sense that there is no "best practice".  So I'll try to give a longer answer at a later time (on holiday at the moment).  But just to get you started ;)

This is arguably the most awkward area of Extempore programming at the moment, and I'm still feeling my way towards the best approach.  So, there is no 'style guide' as yet - indeed I'm very interested in any thoughts you have.

In short yes, you do need to define a cleanup routine, and you should generally try to avoid 'directly' calling anything like what you describe from inside dsp:DSP.  Instead you should go via some external routine, or global var, that can be managed independently from dsp:DSP.  In other words, dsp:DSP should simply reference data managed elsewhere. 

I should mention in passing that dsp:DSP really is a bit of a special case, as the primary (synchronous) audio callback routine it has performance constraints not common to most xtlang routines and thus has some *special* requirements.  Like any audio callback routine in any environment you should only use preallocated memory, not be creating/destroying system resources etc. etc..

As a general rule of thumb though you'll need to create your own allocation/de-allocation routines.  These can either work on the principle of using/freeing heap allocated memory (i.e. halloc/free) - or alternatively you can use zone memory which is manually pushed/popped (this is actually very handy).  Additionally, and just FYI it is also possible to dispose of zone memory temporally - i.e. mark a zone to be automatically freed at a given point in time into the future.  In short, by default a zone has a 'static' bounds but it can also be managed manually or temporally - which can allow you to store a zone for later cleanup.

;; a function that creates a zone, and makes the zone active (pushes the zone)
;; then allocates to the zone
;; pops the zone (deactivates the current zone without destroying it)
;; routine then returns a reference to the zone
(bind-func test_zone_for_toby
   (lambda ()
      (let ((z1 (push_new_zone 1024)))
         ;; do a bunch of zone allocation here ...
         (pop_zone)
         z1)))

at some later point you can then call

(llvm_zone_destroy z1) ;; to cleanup z1's memory

Note, that this does not help you with the automatic zone alloc/decalloc for top level closures - which you have no control over.  However, you can easily avoid using these toplevel 'automatic' zones with a little forethought.

In regards to 'other' resources it's worth keeping in mind that if you have something like this...

(bind-func dsp:DSP
   (let ()
      (my_resource_loading)
      (lambda (time chan ...)
          (random))))

Then everytime you recompile dsp:DSP your external my_resource_loading function will be called - which may or may not choose to load/refresh resources etc..

Hope this all gives you something to mull over.

Cheers,
Andrew.


--
You received this message because you are subscribed to the Google Groups "Extempore" group.
To unsubscribe from this group and stop receiving emails from it, send an email to extemporelan...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Ben Swift

unread,
Jul 28, 2015, 7:25:54 PM7/28/15
to extemp...@googlegroups.com
Hey Toblerone

Good question. AFAIK (although Andy may weigh in on this later) there's
no easy way to add a "zone realloc" hook.

I think the way to do it is to have any malloc-ed resources as global
vars, which you then call free on explicitly when necessary. Often this
isn't too problematic, as these resources can be long-lived, and you
maybe don't need to refresh them with each closure re-compilation.

There are ways to make "poor man's hooks", e.g. setting a null pointer
in the top-level let and then null? testing it in the closure body,
setting it to an (initialised) data structure if necessary.

Maybe if you have a specific example in mind I can make more specific
suggestions?

Cheers,
Ben

Toby Gifford

unread,
Jul 29, 2015, 12:39:26 AM7/29/15
to extemp...@googlegroups.com
Thanks for your answers guys, that makes sense.

For context I have attached my YbotAudioFileReader library below. The idea of it is to 'stream' an audio file from the disk, and allow for seamless random access playback, but without having to load it entirely into memory.

As usual my head is stuck in Object-Oriented mode, so probably my whole approach would not be in the extempore style guide (if there were such a tome). As such i've defined a YbotAudioFileReader data type, with accompanying create/destroy functions. This in turn contains two instances of my YbotAudioFileBuffer type, which is shamelessly borrowed from the existing AudioBuffer type, but additionally has a pthread_mutex, to allow for thread safety between the audio thread and a worker thread that loads new data on request.

The YbotAudioFileBuffer type similarly has create/destroy functions, which basically call (mutex_create) and (mutex_destroy). And here's the rub, because these xthread library functions call malloc under the hood.

Now, the vibe I had been getting about the extempore way suggested the following would be sensible:
(bind-func dsp:DSP
  (let* ((playhead:i64 0)
         (playing:i64 1)
         (gain:float 0.5)
         (reader (YbotAudioFileReader_c "/path/to/my/audiofile.wav" (* SR 10)))
         (val:float 0.0))
    (lambda (in:SAMPLE time:i64 channel:i64    data:SAMPLE*)
      (if (< 0 playing)
          (set! val (reader playhead channel))
          (set! val (dtof 0.0)))
      (cond ((= channel 0)
             (set! playhead (+ playhead 1))
             (cond ((> playhead (reader.frames))
                    (println "Looping")
                    (set! playhead 0)))))
      (* gain val))))


where the various things that the dsp function needed were allocated in the header (if that's the right term) of the closure. But of course, this leaks the underlying mutex's lurking in reader on recompile.

So, I guess what you are saying is that I should be defining a global reader instead?


(bind-func my_audiofile_reader (YbotAudioFileReader_c "/path/to/my/audiofile.wav" (* SR 10)))

(bind-func dsp:DSP
  (let* ((playhead:i64 0)
         (playing:i64 1)
         (gain:float 0.5)
         (reader my_audiofile_reader)
         (val:float 0.0))
    (lambda (in:SAMPLE time:i64 channel:i64    data:SAMPLE*)
      (if (< 0 playing)
          (set! val (reader playhead channel))
          (set! val (dtof 0.0)))
      (cond ((= channel 0)
             (set! playhead (+ playhead 1))
             (cond ((> playhead (reader.frames))
                    (println "Looping")
                    (set! playhead 0)))))
      (* gain val))))






 


ybot_audio_file_reader.xtm

Ross Bencina

unread,
Jul 30, 2015, 7:19:33 AM7/30/15
to extemp...@googlegroups.com
On 29/07/2015 2:39 PM, Toby Gifford wrote:
> For context I have attached my YbotAudioFileReader library below. The
> idea of it is to 'stream' an audio file from the disk, and allow for
> seamless random access playback, but without having to load it entirely
> into memory.

Hi Toby,

Maybe my paper on how to do file streaming without mutexes or allocation
in the audio thread will help:

http://www.rossbencina.com/code/interfacing-real-time-audio-and-file-io

It doesn't get around the apparent problem of extempore's lack of
finalizers, but it might give you some ideas.

Cheers,

Ross.

Toby Gifford

unread,
Aug 1, 2015, 12:52:56 AM8/1/15
to extemp...@googlegroups.com
Thanks Ross, that's very cool! And topical. That'll larn me for not reading recent ACMC proceedings :)

My al-foil & chewing-gum answer to avoiding 'unbounded priority inversion' in the audio-thread is to only ever call pthread_trylock in that thread. The file-I/O-thread on the other hand calls pthread_lock. The critical section in the file-I/O-thread just needs to change the value of a pointer, so I assume it would be unusual for this lock to be held right when the audio-thread tries it. And in the (presumably rare) cases when it does a simple Linear Predictive Coding extrapolation from the last few samples (the trylock is done on a sample by sample basis) should be fairly imperceptible? I guess its possible that the file-I/O-thread gets suspended after locking by a higher priority thread, and so the audio-thread ends up missing a whole buffer. Maybe giving the worker thread real-time priority with a super-short duty cycle would help?

Anyway, i'm probably better off implementing your lock-free approach.


Ross Bencina

unread,
Aug 1, 2015, 1:58:08 AM8/1/15
to extemp...@googlegroups.com
On 1/08/2015 2:52 PM, Toby Gifford wrote:
> Thanks Ross, that's very cool! And topical. That'll larn me for not
> reading recent ACMC proceedings :)
>
> My al-foil & chewing-gum answer to avoiding 'unbounded priority
> inversion' in the audio-thread is to only ever call pthread_trylock in
> that thread.The file-I/O-thread on the other hand calls pthread_lock.

It sounds great if you can make it work. I never looked deeply into
try-locks. I would love to see a streaming scheme using try-locks that
is proven 100% reliable. Although your mention of LPC loss concealment
suggests you aren't there yet.

Lock-free isn't a panacea. It has taken more than 10 years for me to get
any confidence in using lock-free algorithms -- and even then the
pitfalls are myriad.


> The critical section in the file-I/O-thread just needs to change the
> value of a pointer, so I assume it would be unusual for this lock to be
> held right when the audio-thread tries it.

I'm aiming for something where the inter-thread communication is more
reliable than "probably won't fail." My end goal is wait-free,
guaranteed O(1) for every sample. The approach in the paper I mentioned
is a first step in that direction. It is worst-case O(N) per buffer-flip
in the length of the buffer queue. The audio->IO queue in the paper is
not wait free.


> And in the (presumably rare)
> cases when it does a simple Linear Predictive Coding extrapolation from
> the last few samples (the trylock is done on a sample by sample basis)
> should be fairly imperceptible?

From my limited experience with LPC packet loss concealment, I'd
suggest that it might be perceptible under some circumstances. I'd also
be worried about the CPU overhead.

The approach that I take is to buffer (prefetch) some amount of data in
the audio thread, so that any I/O jitter is masked. There needs to be
enough buffering to mask the worst-case latency of I/O operations. In
other words, it doesn't really matter if data is delayed a little bit,
the audio thread always has enough prefetched data to work with.


> I guess its possible that the
> file-I/O-thread gets suspended after locking by a higher priority
> thread, and so the audio-thread ends up missing a whole buffer. Maybe
> giving the worker thread real-time priority with a super-short duty
> cycle would help?

I would certainly suggest making the I/O thread run at higher priority
than most other threads, but lower priority than the audio thread.

Both the I/O thread and the audio thread need to be guaranteed some CPU
time in order to avoid drop outs. Buffering between the IO and audio
thread can mask jitter in IO thread scheduling (whether caused by
pre-emption or waiting for IO operations), but it still requires that
the jitter is bounded.

On a single CPU system, assuming audio runs at higher priority than I/O,
it could be possible to starve the IO thread with very high audio CPU
utilisation. I don't have a solution for that. It's less of an issue
with multi-core systems.


> Anyway, i'm probably better off implementing your lock-free approach.

Even if you don't go the lock-free route, I do think that it would be
worth looking at the paper to see how I modeled the problem.

The paper uses message passing via lock-free queues. This is relatively
easy to reason about correctness, which is the main advantage of the
approach. Another key feature is that the algorithms allow for lock-free
memory management of asynchronously consumed buffers, without a garbage
collector. You still need to explicitly close a stream though -- if you
don't, resources leak.

Good luck!

Ross.



> On Thu, Jul 30, 2015 at 9:19 PM, Ross Bencina <ro...@audiomulch.com
> <mailto:ro...@audiomulch.com>> wrote:
>
> On 29/07/2015 2:39 PM, Toby Gifford wrote:
>
> For context I have attached my YbotAudioFileReader library
> below. The
> idea of it is to 'stream' an audio file from the disk, and allow for
> seamless random access playback, but without having to load it
> entirely
> into memory.
>
>
> Hi Toby,
>
> Maybe my paper on how to do file streaming without mutexes or
> allocation in the audio thread will help:
>
> http://www.rossbencina.com/code/interfacing-real-time-audio-and-file-io
>
> It doesn't get around the apparent problem of extempore's lack of
> finalizers, but it might give you some ideas.
>
> Cheers,
>
> Ross.
>
>
> --
> You received this message because you are subscribed to the Google
> Groups "Extempore" group.
> To unsubscribe from this group and stop receiving emails from it,
> send an email to extemporelan...@googlegroups.com
> <mailto:extemporelang%2Bunsu...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.
>
>
> --
> You received this message because you are subscribed to the Google
> Groups "Extempore" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to extemporelan...@googlegroups.com
> <mailto:extemporelan...@googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout.

--
===================================
AudioMulch 2.2 modular audio software for PC and Mac
http://www.audiomulch.com
---
Everything else: http://www.rossbencina.com

Toby Gifford

unread,
Aug 1, 2015, 2:16:48 AM8/1/15
to extemp...@googlegroups.com
Thanks Andrew for these ideas. I'm not sure that I understand the fundamental computer science issues well enough to proffer any useful thoughts on style yet, however I can share my attempts to grapple with the concepts, in case it's helpful to others on this list.

I'm beginning to grok the zone thing. It clicked for me when I sat down and tried to really understand LISP. The following article I found quite helpful in general http://www.defmacro.org/ramblings/lisp.html

In particular I thought about how I would implement memory management in a language with closures and continuations, and zones suddenly made sense. In other languages where local variables don't persist after a function call returns, the Stack nicely takes care of efficiently zapping them. For them to persist longer clearly requires some other approach, and heap allocation seems like a painful and inefficient way to do this.

I presume this is kinda what the closure's zone is (though this is where I get a bit hazy) - a way for the environment to get efficiently zapped in one fell swoop at some point, whilst still being able to hang around longer than the function call. Where i'm hazy is at what point this zapping does actually occur. For user-created zones that's clear (it's when the user calls zap-zone), but for closure-zones it happens when the symbol bound to it gets rebound?

Regarding user-defined cleanup routines, I don't find any problem (or awkwardness) with needing to create these. The more straight C programming I do, the more I find I prefer it over C++. Hell, if you're going to have to define a destructor anyway, what's the difference? As long as everything in the function (or object) hierarchy cascades cleanup duties down the tree.

Similarly the zone-approach seems to deal with this 'cascading' of cleanup by virtue of the current memzone enclosing the portion of the call-tree below it, at least so long as all the memory is either salloc'ed, zalloc'ed, or explicitly freed after being halloc'ed. 

But where I come a'cropper is when trying to 'mix-metaphors'. Basically when some closure variable does some halloc (or malloc), the zone zapping approach doesn't 'cascade' for this. Its only really a problem when you don't get the chance to call an explicit cleanup routine, which is really only when the symbol gets rebound at the top-level (is that right?)

Where i'm a bit confused is regarding your comment on the code

(bind-func dsp:DSP
   (let ()
      (my_resource_loading)
      (lambda (time chan ...)
          (random))))

where you say (my_resource_loading) gets called each time the function is recompiled. Well, yes, but if the resources acquired are residing in some closure variable, then the recompilation won't have access to that variable to cleanup the resources will it? Or are you saying that the closure environment of the dsp closure persists between compilations? What if I recompile like this?
(bind-func dsp:DSP
      (lambda (time chan ...)
          (random))))


On the other hand, in the particular example I have (of an audio file streamer), the memory leak of a two mutex's is trivial, and I would be hard pressed to manually recompile the billion times it would require to cause a problem. So maybe that's an answer: for the only situation where its difficult to explicitly cleanup—namely the live-coding interactive environment—just treat  the whole computer memory as a giant zone that gets zapped at the end of the performance? Seems a little unsatisfactory though, also given there's other resources than memory that might get fought over.

Ross Bencina

unread,
Aug 1, 2015, 2:53:32 AM8/1/15
to extemp...@googlegroups.com
On 1/08/2015 4:16 PM, Toby Gifford wrote:
> Regarding user-defined cleanup routines, I don't find any problem (or
> awkwardness) with needing to create these. The more straight C
> programming I do, the more I find I prefer it over C++. Hell, if you're
> going to have to define a destructor anyway, what's the difference? As
> long as everything in the function (or object) hierarchy cascades
> cleanup duties down the tree.

The advantage of C++ destructors is that you usually don't have to
remember to call them -- they get called automatically, either when the
scope ends, or when the containing object gets deleted. This provides a
lot of automatic clean-up "for free." When combined with smart pointers,
as is the norm in modern C++, it is rare to have to manually delete
anything.

Even in C, in spite of your claims to the contrary, it is error-prone to
manually call cleanup functions in the presence of arbitrarily placed
return statements. The problem is much worse in C++ with exceptions.


But I think that the main unaddressable issue in this discussion is how
to guarantee cleanup of non-memory resources. e.g. A file handle. In
order to *guarantee* cleanup of such things you need something like
finalizers. Or perhaps cleanup functions registered with a zone. Even
so, there are many arguments against finalizers:

https://en.wikipedia.org/wiki/Finalizer#Problems

A key question is: does the language offer "the freedom to leak
resources" as a feature. C and C++ do offer this awesome feature ;)

Ross.

Toby Gifford

unread,
Aug 1, 2015, 5:21:29 AM8/1/15
to extemp...@googlegroups.com
Actually, I think I sold my audio-file streaming code short. I *think* that its 100% guaranteed to be real-time-safe, under the condition that the system is 100% capable of reading the audio data from file in 2 x real-time on average over the half buffer period (i.e. if using 10 seconds worth of RAM, then system is reliably able to load 10 seconds worth over any given 5 second period).

Even if the worker thread gets suspended after locking the idlebuffer mutex, the audio thread won't care until the half-a-buffer later.

I wouldn't have a clue how to formally prove that though ...

To unsubscribe from this group and stop receiving emails from it, send an email to extemporelan...@googlegroups.com.

Ross Bencina

unread,
Aug 1, 2015, 10:23:17 AM8/1/15
to extemp...@googlegroups.com
On 1/08/2015 7:21 PM, Toby Gifford wrote:
> Actually, I think I sold my audio-file streaming code short. I *think*
> that its 100% guaranteed to be real-time-safe, under the condition that
> the system is 100% capable of reading the audio data from file in /2 x
> real-time/ on average over the half buffer period (i.e. if using 10
> seconds worth of RAM, then system is reliably able to load 10 seconds
> worth over any given 5 second period).
>
> Even if the worker thread gets suspended after locking the idlebuffer
> mutex, the audio thread won't care until the half-a-buffer later.

The next step is making stream creation, destruction and seeking
real-time safe.


> I wouldn't have a clue how to formally prove that though ...

Maybe divide time up in to time-ranges, and argue that certain events
are guaranteed to occur without contention, given some periodicity
assumptions.

You could look into model checking (TLA+, SPIN).

R.

Ben Swift

unread,
Aug 4, 2015, 7:55:06 PM8/4/15
to extemp...@googlegroups.com
Hi Ross, Toby

You're right that the lack of a destructor means that RAII-style
resource management is tricky/impossible.

When the resource is *memory*, then memory zones (including new ones
created with =letz=) provide a nice answer to the problem, but as Toby
points out that won't work for file descriptors, etc.

On the plus side, the lack of a =return= keyword means that it's often
not quite as tricky to figure out where the cleanup code should go.

If you do figure out any cool new idioms to handle this problem in
xtlang, though, I'm all ears :)

Cheers,
Ben

Andrew Sorensen

unread,
Aug 5, 2015, 12:02:15 PM8/5/15
to extemp...@googlegroups.com

I presume this is kinda what the closure's zone is (though this is where I get a bit hazy) - a way for the environment to get efficiently zapped in one fell swoop at some point, whilst still being able to hang around longer than the function call. Where i'm hazy is at what point this zapping does actually occur. For user-created zones that's clear (it's when the user calls zap-zone), but for closure-zones it happens when the symbol bound to it gets rebound?

Zapping occurs at the end of a zones lexical scope - except when it doesn't ;)  Here is a quick overview of the *common* case.

Each Extempore process has it's own stack of extempore zones.  At the bottom of this stack is a single *toplevel* zone.  In effect this zone is actually something to just leak into - as it will not be cleared until the extempore process is destroyed - which is generally something that only occurs when we exit the program.  The *toplevel* (i.e. bottom) stack is also a good place to store persistent global variables.  Extempore zones will grow as necessary.

If you don't do anything special you will always allocated into this *toplevel* zone.

Instead, you can (and should!) create your own zones which get pushed onto the processes stack of zones - commonly using letz. Once you have pushed a new zone onto the processes zone stack any further zalloc's (remembering that alloc is short for zalloc) made, including from within any calls made within the scope of the letz, are allocated into that new zone.  When execution reaches the end of the letz lexical scope, the new zone is popped from the stack and it's memory is zapped.  You can (and should) push zones recursively (i.e. use letz inside another letz).

Note that closures (procedure calls) do not get zones automatically, you must create a zone manually (letz or similar) to push new zones onto the processes zone stack.

This is the *general* behaviour of zones in extempore.

There is however, a special case for toplevel closures - and ONLY for toplevel closures (i.e. not for any other use of lambda).  When you create a toplevel closure, any variables closed over by the closure are allocated into an isolated zone (i.e. a zone that does not belong to any extempore processes zone stack), whose extent lasts as long as the closure lasts (i.e. until the application closes or the closure is recompiled).  Note that this *closure* zone only applies to captured variables (i.e. a let before the body of the closure) - not to the body of the closure.  The body of the closure, when called, will use the top of the processes zone stack as always (i.e. the general case).

There is one final semi-special case, which is that zones *can* be explicitly created (i.e. with a returned reference), and then explicitly pushed, and popped from the processes zone stack.  Where this can be useful is that you pop a zone, without zapping it.  This then allows you to store the zone for an indefinite period of time - pushing and popping it as required, and then zapping it at some point in the future.  Which is not the general case, but can be quite useful.  In other words, zones are first class.
 

Regarding user-defined cleanup routines, I don't find any problem (or awkwardness) with needing to create these. The more straight C programming I do, the more I find I prefer it over C++. Hell, if you're going to have to define a destructor anyway, what's the difference? As long as everything in the function (or object) hierarchy cascades cleanup duties down the tree.

Yes, by and large I agree.
 

Similarly the zone-approach seems to deal with this 'cascading' of cleanup by virtue of the current memzone enclosing the portion of the call-tree below it, at least so long as all the memory is either salloc'ed, zalloc'ed, or explicitly freed after being halloc'ed. 

Yep, as above (memzone and letz being interchangeable in the above discussion).
 

Where i'm a bit confused is regarding your comment on the code
(bind-func dsp:DSP
   (let ()
      (my_resource_loading)
      (lambda (time chan ...)
          (random))))

What I was driving at was not cleaning up using (my_resource_loading) ... which as you say it doesn't help with.  But instead using (my_resource_loading) to ensure that the resource was only loaded once, no matter how many times you recompiled dsp:DSP. The first time you compile dsp:DSP my_resource_loading loads up some resource and returns it - but also sets a flag so that in the future it will simply return the existing resource.

That all said, I really like the idea of a zone based hook.  Something like this:

(bind-func testf
  (lambda ()
    (letz ((file (fopen "/tmp/test" "w")))
      (cleanup (fclose file))
      (if (null? file) (println "Error opening file")
          (begin
            (fprintf file "%s!!!" "Hello World")
            void)))))
            
(testf)

Where cleanup takes an arbitrary expression and gets applied just before the zone gets zapped.  This means you can put your cleanup code close the allocation - which in the above case isn't such a huge plus.  But think about this library code example - note the use of let not letz inside CreateFile "library" code.

(bind-func CreateFile
  (lambda (path a)
    (let ((file (fopen path a)))
      (cleanup (fclose file))
      file)))

(bind-func testf2
  (lambda ()
    (letz ((file (CreateFile "/tmp/test" "w")))
      (if (null? file) (println "Error opening file")
          (begin
            (fprintf file "%s!!!" "Hello World")
            void)))))
            
(testf2)


I'll have a bit more of a think about this but I think something like cleanup might be a good idea.

Cheers,
Andrew.

Andrew Sorensen

unread,
Aug 5, 2015, 12:08:30 PM8/5/15
to extemp...@googlegroups.com
Yes, we also offer this amazing feature.  Unfortunately, as is so often the case, the trouble with not offering this amazing feature, is the other amazing features that you lose.  

FYI I have been thinking for a while about some kind of ownership system but not willing to jump down that hole just yet.

Toby Gifford

unread,
Jan 23, 2016, 8:53:28 PM1/23/16
to extemp...@googlegroups.com
Did this 'cleanup' idea end up making its way into extempore? I'm trying to use the Apache Portable Runtime library, and hoping to do something like this:

(sys:load "libs/external/apr.xtm")

(bind-func yfs_open:[apr_file_t*,i8*]*
  (let ((pool:apr_pool_t* (apr_pool_create)))
    (cleanup (apr_pool_destroy pool))
    (lambda (path)
      (let ((handle_ref:apr_file_t** (zalloc)))
        (apr_file_open handle_ref path APR_FOPEN_READ APR_FPROT_OS_DEFAULT pool)
        (pref handle_ref 0)))))


But the compiler says it doesn't know about the symbol 'cleanup'.




Andrew Sorensen

unread,
Jan 23, 2016, 9:02:24 PM1/23/16
to extemp...@googlegroups.com, Toby Gifford
Hey Toby,

Yes, should definitely work. I might have renamed it zone_cleanup ??
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
Reply all
Reply to author
Forward
0 new messages