Skip to content

Make weak handles created via Dart_NewWeakPersistentHandle not auto-delete. #42312

@mkustermann

Description

@mkustermann

Right now the VM's weak handle API exposed to embedders is a bit fragile to use: After creation of a weak persistent handle via Dart_NewWeakPersistentHandle it is only save to use it (e.g. via Dart_HandleFromWeakPersistent, Dart_DeleteWeakPersistentHandle) if one knows that the object itself is still alive.

Once the object in the weak persistent handle is unreachable any following Garbage Collection can reclaim the object and in doing so will delete the weak persistent handle as well.

See the code in scavenger.cc:

  void VisitHandle(uword addr) {
    auto handle = reinterpret_cast<FinalizablePersistentHandle*>(addr);
    ObjectPtr* p = handle->raw_addr();
    if (scavenger_->IsUnreachable(p)) {
      handle->UpdateUnreachable(thread()->isolate_group());
    }
    ...
  }

which eventually calls dart_api_impl.cc:FinalizablePersistentHandle::Finalize

  ...
  state->FreeWeakPersistentHandle(handle);

=> The only usage pattern when this is safe is when the weak handle is only accessed if the code is guaranteed to have a strong reference to it (i.e. the object in the weak handle is still alive).

If the object is being deleted by the GC and native code tries to delete the weak handle on another thread (or get the object out of the weak handle) by calling the Dart API then the two threads are racing and it might lead to undefined behavior. It is not possible to prevent this via simple locking because that can lead to a deadlock.

Safe usage pattern

One usage pattern which is safe is e.g. this:

  1. A resource class inherits from a native field wrapper class, e.g. class _File extends NativeFieldWrapperClass1 { ... }
  2. The constructor of such an object will make a new weak handle via Dart_NewWeakPersistentHandle from the file object handle and set the native field of the file to be this weak handle via Dart_SetNativeInstanceField.
  3. An explicit file.close() will call native code and get the weak handle from the file object and deletes it via Dart_DeleteWeakPersistentHandle.

Notice that step 3 guarantees that the file object is still alive when the weak handle gets deleted.

In case a user forgets to explicitly close the file, the GC will invoke the weak handle callback and automatically deletes the handle afterwards.

Proposed changes

We should consider changing our existing weak handle API to not auto-delete and migrate our embedders.

We can then introduce a new API to support finalization and safe auto-delete of finalizable handles by requiring the user to proof he/she has access to the object being eagerly finalized, e.g.

void Finalizer(int fd) {
   close(fd);
}

void FUNCTION_NAME(File_File)(Dart_NativeArguments args) {
  Dart_Handle file_object = ...;
  int fd = open(...);
  auto finalizable_handle = Dart_NewFinalizableHandle(file_object, Finalizer, fd);
  Dart_SetNativeInstanceField(file_object, 0, fd);
  Dart_SetNativeInstanceField(file_object, 1, finalizable_handle);
}

void FUNCTION_NAME(File_Close)(Dart_NativeArguments args) {
  Dart_Handle file_object = ...;
  auto fd = Dart_GetNativeInstanceField(file_object, 1);
  auto finalizable_handle = Dart_GetNativeInstanceField(file_object, 1);
  Dart_DeleteFinalizableHandle(finalizable_handle, file_object);
  close(fd);
}

By forcing the programmer to provide the original object to Dart_DeleteFinalizableHandle we know it's still alive.

/cc @rmacnak-google @mraleph @dcharkes @alexmarkov

Metadata

Metadata

Assignees

Labels

area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.library-ffi

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions