Page 1 of 1

Preventing lifetime analysis

Posted: Mon Apr 18, 2011 2:17 am
by frisky
Hi guys,

I'm busy writing a partial binding (not at all ready for any sort of release, I blame my thesis) of the OpenCV library. To integrate garbage collection, I've wrapped key foreign objects with clos objects and attached a finalizer to the clos wrappers. Now, the problem is that sometimes, if you use the foreign pointer associated with the wrapper, the wrapper itself becomes no longer needed and a smart compiler might decide to make it available for garbage collection. Eg.

Code: Select all

(let ((foreign-object foo))
  (foreign-function (foreign-pointer foreign-object)))
Note that, after foreign-pointer has been called, foreign-object is "no longer needed" from a naive point of view, and might be garbage collected before foreign-function is called (thus invalidating the foreign pointer and causing nastiness).

Is there any portable way I could prevent this? I know settings (safety 3) is supposed to stop this in SBCL, but am unsure whether this is true across implementations. Any other solution would be nice also.

Thanks :)

Re: Preventing lifetime analysis

Posted: Mon Apr 18, 2011 4:43 am
by ramarren
This does not really look like something that can occur in practice, since, even considering lifetime analysis, it would require the GC to fire at the exact wrong moment. And how often do you pass object with no other references to foreign functions? If this pattern is common you might consider not using finalized wrappers by default, but rather primarily control lifetime with WITH- macros and only create wrappers for objects with longer lifetime which are unlikely to be consumed this way.

A more elaborate solution would to be to wrap all foreign functions with a function that flags the foreign object as needed, which finalizer checks and rather than freeing the foreign object sets another flag, which the wrapper checks and frees object if the latter is set, and clears the first flag otherwise.

Re: Preventing lifetime analysis

Posted: Mon Apr 18, 2011 7:18 pm
by nuntius
You also have the opposite issue: The CL GC doesn't see much cost with holding foreign pointers around and may never see the need to free them...

I don't think finalizers are good for your intended purpose. Actually, I'm not sure they are much good anywhere. In Java, finalizers aren't even guaranteed to be called.

I think we would be better off with an exact boost::shared_ptr-style reference counting scheme in CL, but haven't yet found the right way to implement it. [Implementation is easy. Making it natural to use is a bit harder, and my experiments involve a generalized with- macro.]

Re: Preventing lifetime analysis

Posted: Mon Apr 18, 2011 11:19 pm
by Warren Wilkinson
Here is one idea.

Make a big array of pointers to foreign data elements. Then make your CLOS objects hold an index into this rather than an actual pointer. When the CLOS object is freed, the finalizer just pushes the index number onto a todo list.

Then, at some safe-point in the code, you go through the todo list, follow the indexes and free the pointers.

Its not ideal, but it might solve the problem.

Re: Preventing lifetime analysis

Posted: Tue Apr 19, 2011 8:59 am
by Kompottkin
You could temporarily put the wrapper somewhere the GC will notice it, such as a multiset (FSet calls this a “bag”) referenced by some global variable, and remove it again at the end of the context. Of course, in case your code is multi-threaded, you will have to make sure that access to the data structure is thread-safe.

The good thing about this approach is that writing a macro for it should be pretty straight-forward. The bad thing is that depending on the circumstances, it may be prohibitively costly CPU-wise, especially in the case that access to the global multiset needs to be synchronized across multiple threads.

(Which makes me wonder... Is there a library that provides an efficient asynchronous concurrency primitive akin to Clojure's “agents”? Such a thing might be useful now and again.)

Re: Preventing lifetime analysis

Posted: Wed Apr 20, 2011 12:45 am
by frisky
Hi, thanks for all your replies! :)

With regard to some of the reservations, the problem occurs only very, very rarely, but having multithreaded code that runs for days with heavy heap allocation seems to be enough to trigger it. The clue to this was finding that the call to the foreign function bombed because the structure looked deallocated to the foreign code on very rare occasions (a pointer within the structure gets set to NULL to indicate deallocation).

My temporary fix was to do something like this (which is sort-of along the line of what Kompottkin suggested):

Code: Select all

(let ((foreign-object foo))
  (prog1 (foreign-function (foreign-pointer foreign-object)))
	 (car (list foo))))
This made things run smoothly, but I'd have a nagging feeling about some smart compiler noticing the subterfuge (Maybe using a more complex object, like from FSet, would make it more future proof, although I guess a call to any user-defined dummy function will do at that point.). Writing a macro to automate this would probably be the way to go, it just seems a bit sad given that the point of the foreign object wrapper is to make GCing of foreign objects safe and transparent. Anyway, maybe I should stop moaning and bite the bullet? :)

Re: Preventing lifetime analysis

Posted: Thu Apr 21, 2011 2:28 pm
by Kompottkin
frisky wrote:This made things run smoothly, but I'd have a nagging feeling about some smart compiler noticing the subterfuge ...
You may want to declare the variable SPECIAL, which will at least prevent the compiler from determining statically that the declared variable is not needed. In that case, you can also omit the (car (list ...)) trick. Since FOREIGN-FUNCTION may be redefined at any time, optimizing a special variable away is only possible during program execution, and only if the runtime system can prove that FOREIGN-FUNCTION will neither redefine itself nor access FOREIGN-OBJECT by any means (including SYMBOL-VALUE). I highly doubt there is a compiler that is actually able to do that for non-trivial cases, especially when foreign code is involved.

Of course, it's still not a 100% safe approach; but depending on your needs, it may well be good enough.