I've actually simplified my program that causes the issues and it doesn't need the callback or the foreign functions at all it just needs structs with substructs.
This shows that if garbage is collected after making the substruct is complete then the finalizer gets run, which trashes the memory I'm about to read. The interior memory will-registration would solve this, but given that the docs say that it shouldn't be used for small objects I don't want to use it for every slice object in my program. I think I need to come up with a different method for handling garbage collection of structs, or possible judicious use of the recently added void/reference-sink once the new release is out. #lang racket/base (require ffi/unsafe ffi/unsafe/alloc) (define-cstruct _grpc-slice-refcounted ([bytes _pointer] [length _size])) (define-cstruct _grpc-slice-inlined ([length _uint8] [bytes (_array _uint8 (+ (ctype-sizeof _size) (ctype-sizeof _pointer) -1))])) (define-cstruct _grpc-slice/ffi ([refcount _pointer] [data (_union _grpc-slice-refcounted _grpc-slice-inlined)])) ;; Struct that hides the raw pointer from the exposed api (struct grpc-slice (pointer)) (define (grpc-slice-length slice) (define ffi-slice (grpc-slice-pointer slice)) (if (grpc-slice/ffi-refcount ffi-slice) (grpc-slice-refcounted-length (union-ref (grpc-slice/ffi-data ffi-slice) 0)) (grpc-slice-inlined-length (begin0 (union-ref (grpc-slice/ffi-data ffi-slice) 1) (collect-garbage))))) (define (make-grpc-slice) (define p (ptr-ref (malloc _grpc-slice/ffi) _grpc-slice/ffi)) (memset p #x00 1 _grpc-slice/ffi) ;; Would call slice-unref which might free the memory in the real code. (register-finalizer p (lambda (p) (memset p #xFF 1 _grpc-slice/ffi))) (grpc-slice p)) (define s (make-grpc-slice)) (grpc-slice-length s) (grpc-slice-length (begin0 s (set! s #f))) On Tue, Oct 3, 2017 at 7:55 PM, Eric Dobson <eric.n.dob...@gmail.com> wrote: > Parsing it out in atomic mode probably will work, I'll look at that. The > callback method I described is actually not how it works, and it is a bit > more complicated (involving a separate place and blocking foreign calls), > but I can do all of the decomposing work in atomic mode. I think I'm > avoiding atomic mode because in a multithreaded system it would be somewhat > expensive operation, but that might not hold in Racket. > > The other option I'm looking at is to construct the racket child struct > reference before handing it off to the foreign library, and having them be > responsible for deallocating their fields and then decrementing a ref count > on the parent whose will will only be responsible for freeing the direct > memory. > > The will functionality I'm describing is different than current wills as > current wills are ready when the cpointer is unreachable not when the > memory pointed to by the cpointer is collectable. > > #lang racket > (require ffi/unsafe) > > > (define p1 (malloc 'atomic-interior 100)) > > (define port (current-output-port)) > (register-finalizer p1 (lambda (p1) (displayln 'dead port))) > (define p2 (ptr-add p1 10)) > (set! p1 #f) > (collect-garbage) > (sleep 4) > > In this example it wouldn't print "dead" because p2 is still holding the > memory alive even though p1 is dead. But if p2 was cleared then it would > get printed. > > > > On Tue, Oct 3, 2017 at 7:04 PM, Matthew Flatt <mfl...@cs.utah.edu> wrote: > >> Is the work to parse out the values short enough that you can do it >> atomically in response to the notification that says the data is ready? >> If so, it's probably best to parse and free in atomic mode. Or can you >> at least atomically separate out references to children, where >> finalizers can sensibly be attached to the individual references? >> >> If not, then I don't have a better idea than the approach you describe. >> I don't quite follow why you'd need new functionality from wills, since >> a will procedure on a cpointer already receives the cpointer back when >> there are otherwise no references to the cpointer. >> >> At Tue, 3 Oct 2017 18:39:04 -0700, Eric Dobson wrote: >> > I'm dealing with some foreign apis that want to be passed long lived >> output >> > pointers to structs. The apis eventually call back indicating that the >> > struct has been filled in appropriately and then I want to read out the >> > values and deallocate the structs. I'm using atomic interior memory for >> > these structs as they don't need to hold GCable values themselves and >> they >> > need to not be moved while the foreign library has a pointer to them. >> Once >> > they are back I need to construct derived pointers to the sub structs >> and >> > parse out all the different parameters. >> > >> > The issue I'm running into is reliably destroying the struct when I'm >> done >> > parsing out all the values. If it was as simple as freeing the memory, >> it >> > would be easy as I can rely on the GC to do that. But it is not as the >> > struct has pointer fields that may have been initialized by the foreign >> > library and thus I need to actively run destructor code to free those >> > subparts before freeing the struct. This seems like a good use for >> > will-executors, but the issue with those is that they are attached to >> > racket values not the underlying memory object. And in this case because >> > the memory is allocated with 'atomic-interior', it is possible for the >> > original pointer to no longer be needed and only derived pointers >> needed. >> > >> > I'm thinking there may be clever ways with making sure that every >> derived >> > pointer either maintains an explicit reference or increments a reference >> > count on the original pointer, but I'm worried that is very complicated >> and >> > likely to be broken. The easiest solution for me would be to have >> something >> > like a will that could be attached to a 'cpointer?' value and would be >> > called back with a fresh 'cpointer?' value that pointed at the same >> address >> > once there were only weak references to the object. Is this possible or >> > does anyone see a better solution? >> > >> > >> > Example: >> > >> > ;; Child corresponds to a byte array >> > (define-cstruct _child ([ptr _pointer] [len _int]) >> > ;; Parent has two inlined children >> > (define-cstruct _parent ([child1 _child] [child2 _child])) >> > >> > ;; When the parent is to be cleaned up I need to ensure that the two >> > children have their 'ptr' fields passed to free. >> > >> > ;; The issue is in a function that wants to decompose the parent into >> > children pointers and use their values: >> > (define (child->bytes c) <elided>) >> > (define (parent->bytes p) >> > (bytes-append (child->bytes (parent-child1 p)) #"." (child->bytes >> > (parent-child2 p)))) >> > >> > ;; In this after the parent-child2 call, there is no strong reference >> to p >> > so if this was the last reference to it, GC could happen before >> > child->bytes was called and determine that p was unreachable and so any >> > wills would become ready. Thus I cannot free the underlying memory in a >> > will. >> > >> > -- >> > You received this message because you are subscribed to the Google >> Groups >> > "Racket Users" group. >> > To unsubscribe from this group and stop receiving emails from it, send >> an >> > email to racket-users+unsubscr...@googlegroups.com. >> > For more options, visit https://groups.google.com/d/optout. >> >> -- >> You received this message because you are subscribed to the Google Groups >> "Racket Users" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to racket-users+unsubscr...@googlegroups.com. >> For more options, visit https://groups.google.com/d/optout. >> > > -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.