-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
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:
- A resource class inherits from a native field wrapper class, e.g.
class _File extends NativeFieldWrapperClass1 { ... }
- 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 viaDart_SetNativeInstanceField
. - An explicit
file.close()
will call native code and get the weak handle from the file object and deletes it viaDart_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.