-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for capturing errno across calls #38832
Comments
This makes error handling tricky and potentially flaky when dealing with Win32 API calls. Many Win32 APIs return boolean return results (or handles) instead of an error code. It's a lot more widespread for Win32 since |
Encounter the same problem when debug https://github.com/Sunbreak/logic_conf.dart/blob/e4935e68399987d61262b3a7d20d0a2c38808471/lib/src/logic_conf_windows.dart#L54-L58 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I think for desktop applications especially Windows (either dart or flutter), this would be really helpful. Are there any updates regarding this issue? |
|
In .NET Framework. .NET is designed well. |
You mean .NET made a workaround for a poorly designed API, right. |
Comment aside, just save errno when returning back from native code, and restore when calling native code. This is the method LuaJIT uses. https://luajit.org/ext_ffi_api.html#:~:text=Utility%20Functions |
Is there a way to access the errno in a platform-agnostic way? In https://github.com/tvolkert/libjpeg/blob/898aaf01bbf07619cb8a9047f7b732b1b544d01f/lib/libjpeg.dart#L197, the return code will be 0 if there was an error, and errno will contain the actual error -- and I'd like to be able to access it in a platform-agnostic way. |
The idea with the native-assets feature is that the native code can be wrapped in C to catch the error in C and return a tagged union back to Dart. And then use FFIgen on the C wrapper function.
I believe all OSes will be |
Update on this issue: Our current thinking is that people should write wrapper C functions to capture the Work on the native assets feature should enable bundling C code easily with Dart packages. (Currently in experimental.) |
We have a customer that has a valid use case for this where it's not about getting the wrong error message with some low probability but the actual application behavior requires checking the error numbers (see b/323386486). There's a few things
We should validate what's the bets for @dcharkes Could you do some investigations here? |
We already built that once: https://dart-review.googlesource.com/c/sdk/+/240847 @mkustermann What are your thoughts on solving this with native assets instead? (And FFIgen. But win32 has it's own code gen.) |
I was hoping there's a way to do this more efficiently than what that CL does (e.g. load from segment register, store into THR). If it's a high-overhead + high-complexity solution it would certainly push the favor towards codegen.
Certainly open to it, you can prototype changing the win32 codegen and seeing how it would look like in the end. It may push it out until native assets are non-experimental (or some end-users can use pre-release versions that use native assets). |
Was both sad there wasn't an easy fix to this but appreciative that we're tracking the issue. It is fantastic to be able to use |
We have a larger set of features that we could add to |
I wonder if it would be possible to have a special type that indicates that a call should include @Native<[Int, Errno] Function(Int, Pointer<Uint8>, Int)>(isLeaf: true)
external [int, Errno] read(int fd, Pointer<Uint8> buf, int count); The idea would be that calls to The special types that I can imagine now are: |
I had a discussion about this with @mkustermann few months ago - but I have never wrote it down. I think we could just maintain an invariant that errno / GetLastError is preserved on the FFI transition boundary. We only destroy it whenever we go into runtime - which means we should be able to simply save and restore it on the runtime transition boundary - and that would be fairly cheap. This would mean that runtime never clobbers We should then some form of |
Unless we bundle a tool chain with the SDK, it would mean that our users need to install their own. |
Is there a single boundary point into the runtime? |
There is a very limited amount of ways to transition from Dart code to native code.
I think that's it. |
I'd like to try to figure this out. |
An alternative approach, suggested by @aam, is to capture As long as we never expose
if (read(...) == -1) {
await Future.delayed(const Duration());
print(__errno());
} I have a POC sketch here: |
That's exactly what we wanted to avoid though. The idea has been considered before, as @mkustermann wrote all the way in 2019:
But we were somewhat hesitant to implement this because it would add 4 memory moves for each FFI transition - which probably is invisible on non-leaf calls, but would likely be visible for leaf calls. On the other hand maybe we overestimated the cost... So we could just benchmark it and see how big the overhead actually is.
It looks reasonable but for non-leaf calls save needs to happen after safepoint transition (and restore before transitioning out of safepoint) - because it is the safepoint transition which destroys it. |
I tested using this patch: https://dart-review.googlesource.com/c/sdk/+/391843/9 Here is a test to demonstrate the semantic change: import 'dart:ffi';
import 'dart:io';
import 'package:ffi/ffi.dart';
@Native<Int Function(Pointer<Utf8>, Int)>(isLeaf: true)
external int open(Pointer<Utf8> buf, int flags);
@Native<Pointer<Int> Function()>(isLeaf: true)
external Pointer<Int> __error();
int get errno => __error().value;
main() async {
print('About to call open (EPERM=1, ENOENT=2)');
final openResult = open("/tmp/doesnotexist".toNativeUtf8(), 0);
print('Open returned: $openResult, errno=$errno');
try {
Directory('/tmp').deleteSync();
} on Exception {}
print("Called `Directory('/tmp').deleteSync()`, errno=$errno");
} Without the patch:
With the patch:
I'm running some benchmarks now but, if there is a significant performance regression, I think that it would be fine to force the developer to specify whether they want to save errors or not e.g. @Native<Int Function(Pointer<Utf8>, Int)>(isLeaf: true, saveErrors: true)
external int open(Pointer<Utf8> buf, int flags); The documentation for final openResult = open("/tmp/doesnotexist".toNativeUtf8(), 0);
await ... // Isolate might be run on a different OS thread.
if (errno == ... and: final errnoPtr = __error();
final openResult = open("/tmp/doesnotexist".toNativeUtf8(), 0);
try {
Directory('/tmp').deleteSync();
} on Exception {}
final errno = errnoPtr.value(); // not an FFI call, errno will not be restored |
Hm, if performance is a concern, what about a parameter to |
The approach is still on the table if I can't get #38832 (comment) to work. |
When calling low level functions, e.g.
libc
wrappers around syscalls, one often gets-1
back anderrno
will be set to the actual error message.When using
dart:ffi
to to do such calls there is no atomicity between theDart -> C
call and an access oferrno
(if we even support that). In fact, arbitrary VM code (e.g. GC), can run in between the two. That can causeerrno
to be clobbered.An option would be to save
errno
into a slot onThread::last_errno_
before returning from the call. We could then add an accessor for thelast_errno_
./cc @sjindel-google @dcharkes
The text was updated successfully, but these errors were encountered: