Is it possible to "cleanup" a generator function?

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).