dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.19k stars 1.57k forks source link

[doc/ffi] `NativeFinalizer` shutdown guarantees #55511

Open dcharkes opened 5 months ago

dcharkes commented 5 months ago

Currently, the documentation states that native finalizers will be run before isolate group shutdown.

The implementation currently guarantees that native finalizers are run on isolate shutdown.

Currently we under-promise, and over-deliver. It might be useful to actually promise run on isolate shutdown. This way users using the isolate APIs can use native finalizers freeing up globals in native code and reuse that native code when a new isolate is started.

Thanks for reporting @mraleph!

@lrhn Any concerns about over-promising things?

Side note: Shared (isolate group) native finalizers

If in the future we introduce shared variables, we'd likely want to have a type of native finalizer that is only guaranteed to run on isolate group shutdown. For such shared native finalizers, we might need a different constructor:

// Adapted from the doc-comment example.

class Database implements Finalizable {
  shared static final _finalizer =
      NativeFinalizer.shared(_nativeDatabaseBindings.closeDatabaseAddress.cast());

If we were to introduce a native finalizer shared in an isolate group with a different constructor, we'd need to update the doc comments to reflect these two different shutdown guarantees for shared and non-shared native finalizers.

The current workaround without the shared keyword is to use Dart_FinalizableHandles from the dart_api.h, these are isolate group scoped instead of isolate scoped.

Edit, also see:

lrhn commented 5 months ago

A native finalizer triggers on an object being unreachable. Currently we assume that isolate shutdown makes every object in the isolate unreachable (and Isolate.exit is treated as a very efficient copy, I guess).

If a variable is shared, its value is shared, and will not become (globally) unreachable if one isolate shuts down. That seems reasonable.

If, big if, we introduce shared variables, and therefore shared values, we should perhaps have shared finalizers too. (If the finalizer object is not shared, it would die with the isolate? So it should be stored in a shared variable too?)

So a separate constructor, or even a separate class with its own documentation, which would answer the question here.

dcharkes commented 5 months ago

Some notes from discussion @mkustermann and @mraleph:

Sharable and isolate local constructor:

NativeFinalizers being GCable: