Home > Blockchain >  Is it possible to "cleanup" a generator function?
Is it possible to "cleanup" a generator function?

Time:01-20

I'm implementing a library that uses dart:ffi to bind functions of PDFium in a object oriented manner. Until now, it is quite easy to translate the C functions into dart and hide the whole pointer stuff behind dispose functions and classes.

Now I want to implement the search part and I kind of want to use a generator (sync or async) to create an Iterable<...>.

The question is now: is it possible to provide some kind of "cleanup code" for a generator function? If the iterator that is returned never completes, it creates memory leaks. Thus, I need a way to cleanup allocated resources after the iterator (or stream) completes.

class Foo {
  Iterable<dynamic> search() sync* {
    var page = getPagePointer();
    for result in page.search() {
      yield result;
    }
    closePagePointer(page); // <-- how can I guarantee this?
  }
}

Maybe there is no way of guaranteeing this, then I will not create a stream/iterator but it would be a nice use-case for a generator function.

Thank you for any help!

CodePudding user response:

Not yet, but "soon".

The problem here is that a sync* returns an iterator, and it's very common for people to just throw away an iterator when they've found the element they were looking for, instead of running it to its end. If they do that, then the closePagePointer function will never be called. Using try/finally won't make any difference, because the code stalls at the yield until someone calls moveNext, and they never do. Or maybe they will, some time in the future. You can never know.

What might work is the Finalizer class or the NativeFinalizer class, both planned for Dart 2.17, if all goes well.

Those will allow you to get a callback when an object is no longer reachable (either best-effort for Finalizer or definitely and eagerly, but only with a native callback, for NativeFinalizer). If you attach such a finalizer to the iterator of the Iterable returned by search, you might be able to release the page if the iterator is no longer being used.

Worst case, someone holds on to the iterator forever, but never calls moveNext again. That's a case you will never be able to distinguish from someone who might call moveNext later.

The slightly more dangerous approach is to time out after some time of not being used, and close the connection. Something like:

Iterable<dynamic> search(
    {Duration timeout = const Duration(seconds: 5)}) sync* {
  var page = getPagePointer();
  for (var result in page.search()) {
    bool timedOut = false;
    Timer timer = Timer(timeout, () {
      timedOut = true; 
      closePagePointer(page);
    });
    yield result;
    if (timedOut) throw TimeoutException("Page has timed out", timeout);
    timer.cancel();
  }
  closePagePointer(page); // <-- how can I guarantee this?
}

That risks timing out too soon, if someone is being slow (like, waiting for user input before continuing).

CodePudding user response:

A StreamController may help. You get callbacks when the stream is no longer listened to.

A Stream is not the same as an Iterable, but very useful and versatile.

  •  Tags:  
  • Related