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.2k stars 1.57k forks source link

provide helpers (or samples) for zero-overhead non-leaking bytes transfer between isolates in ffi #47270

Open mraleph opened 3 years ago

mraleph commented 3 years ago

Users struggle to find effecient ways to pass large buffers of data around without either leaking them or copying them multiple times. We should consider providing some primitives and/or samples in FFI.

A common question is: my worker isolate produced large data array which I am holding by a pointer - how do I send it to main isolate without copying or leaking it?

The best answer right now is to use Dart_PostCObject to send it as an external typed data and simultaneously assign a finalizer to it - but this approach is not very discoverable. We could consider adding samples or helpers for it.

Dart_CObject message;
message.type = Dart_CObject_kExternalTypedData;
message.value.as_external_typed_data.type = Dart_TypedData_kUint8;
message.value.as_external_typed_data.length = length;
message.value.as_external_typed_data.data = reinterpret_cast<uint8_t*>(image);
message.value.as_external_typed_data.peer = image;
message.value.as_external_typed_data.callback = [](void* isolate_callback_data, void* peer) {
 free(peer);  // peer points to image buffer, see above.
};
result = Dart_PostCObject(port, &message);
if (!result) {  // failed to send the message so we own image data.
  free(image); 
}
fzyzcjy commented 3 years ago

After carefully reading the doc, I find that the example above seems incomplete. Please correct me if I am wrong.

  • If true is returned, the message was enqueued, and finalizers for external
  • typed data will eventually run, even if the receiving isolate shuts down
  • before processing the message. If false is returned, the message was not
  • enqueued and ownership of external typed data in the message remains with the
  • caller.

Thus, maybe should add an extra free, when return value is false:

Dart_CObject message;
message.type = Dart_CObject_kExternalTypedData;
message.value.as_external_typed_data.type = Dart_TypedData_kUint8;
message.value.as_external_typed_data.length = length;
message.value.as_external_typed_data.data = reinterpret_cast<uint8_t*>(image);
message.value.as_external_typed_data.peer = image;
message.value.as_external_typed_data.callback = [](void* isolate_callback_data, void* peer) {
 free(peer);  // peer points to image buffer, see above.
};
bool result = Dart_PostCObject(port, &message);
if (!result) {
 free(peer); // because the ownership remains to the caller!
}
mraleph commented 3 years ago

@fzyzcjy good point, I have forgotten that Dart_PostCObject can fail. Changed the example above.

rustonaut commented 2 years ago

It also would be nice to note why we have both peer and data?

fzyzcjy commented 2 years ago

@rustonaut I guess data is for the real data, while peer is for your arbitrary usage. For example, you may assign another data structure to the peer in order to do fancy things in the callback. (Just a guess, I am not an expert)

rustonaut commented 2 years ago

Thanks, I wasn't sure if peer might be related to e.g. some RPC functionality.

If it's "just a void pointer passed to the finalizer" that would be grate as it would allow me to remove one indirection and simplify the usage in dart, too :+1: