-
Notifications
You must be signed in to change notification settings - Fork 27
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
Dart: fix race condition between garbage collection of finalizable objects and native function calls #1633
Dart: fix race condition between garbage collection of finalizable objects and native function calls #1633
Conversation
----- Motivation ----- If the generated class is not marked with 'Finalizable' interface, then, when methods are invoked on its object, the object can be garbage collected in the middle of method execution. For instance let's take a look at the following example from functional tests: 'AsyncClass().asyncVoid(false)' 1. AsyncClass object is created and its lifetime is tied with the native finalizer, which deletes shared_ptr (this.handle) that owns resources. 2. Then, inside 'asyncVoid(false)' we use only 'this.handle' member to pass the opaque handle to the native function. From garbage collector’s after the handle is passed 'this' object is no longer used by anybody. Therefore, it is not needed – can be finalized. 3. If the garbage collection kicks in before the native call, then the 'AsyncClass' object is garbage collected, its finalizer is run and therefore, the resources on C++ side are deleted. It causes the segmentation fault. We have a race condition between the garbage collection and execution of native functions. The consequences can be dramatic -- the finalizer registered for the object may be executed before the native function call inside the member methods. The finalizers delete C++ resources tied with the object. This can lead to segmentation fault. ----- Solution ----- The 'Finalizable' interface is a viable solution proposed by Dart:FFI documentation. It ensures, that 'this' object outlives the whole method call -- it guarantees, that the native function call finishes before the finalizer is run. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
This change adjusts the output of smoke tests for usage of 'class' keyword for Dart. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
If the generated class for interface is not marked with 'Finalizable', then when methods are invoked on its object, the object can be garbage collected in the middle of execution of the method. The consequences can be dramatic -- the finalizer registered for the object may be executed before the native function call inside the member methods. This can lead to segmentation fault. The 'Finalizable' interface ensures, that the object outlives the native function call. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
This change adjusts the output of smoke tests for usage of 'interface' keyword for Dart. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
If the generated class for lambda is not marked with 'Finalizable', then when 'call()' is invoked on its object, the object can be garbage collected in the middle of execution of the method. The consequences can be dramatic -- the finalizer registered for the object may be executed before the native function call inside the member methods. This can lead to segmentation fault. The 'Finalizable' interface ensures, that the object outlives the native function call. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
This change adjusts the output of smoke tests for usage of 'lambda' keyword for Dart. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
If the generated class is not marked with 'Finalizable', then when a method is invoked on its object, the object can be garbage collected in the middle of execution of the method. The consequences can be dramatic -- the finalizer registered for the object may be executed before the native function call inside the member methods. This can lead to segmentation fault. The 'Finalizable' interface ensures, that the object outlives the native function call. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
This change adjusts the output of smoke tests for usage of 'LazyList' for Dart. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
'Finalizer' marker was introduced to FFI in SDK 2.17.0. The CI jobs had been using much older SDK: 2.12. The latest stable Dart SDK is 3.6.0. Therefore, the upgrade is minor. Signed-off-by: Patryk Wrobel <183546751+pwrobeldev@users.noreply.github.com>
The problem was reproducible in functional tests. On average 1 in ~15 runs failed. Please find a few stack traces from functional tests, which show the invalid behavior. Async_test.dart:
Inheritance_test.dart:
ExternalTypes_test.dart:
In all mentioned cases accessing the converted opaque handle caused segmentation faults. The memory was freed because of the race condition. Please find some debug logs from one of failed cases:
The above is an evidence of invalid behavior. |
In order to fix the problem the According to the documentation:
Reference: |
A few notes for reviewers:
The PR is intended to be reviewed commit-by-commit. The non-interesting changes related to the output of smoke tests are present in separate commits. |
----- Motivation -----
If the generated class is not marked with 'Finalizable' interface,
then, when methods are invoked on its object, the object can be
garbage collected in the middle of method execution.
For instance let's take a look at the following example from functional
tests:
'AsyncClass().asyncVoid(false)'
finalizer, which deletes shared_ptr (this.handle) that owns resources.
pass the opaque handle to the native function. For garbage collector
after the handle is passed 'this' object is no longer used by anybody.
Therefore, it is not needed – can be finalized.
'AsyncClass' object is garbage collected, its finalizer is run and therefore,
the resources on C++ side are deleted. It causes the segmentation fault.
We have a race condition between the garbage collection and execution
of native functions.
The consequences can be dramatic -- the finalizer registered for
the object may be executed before the native function call inside
the member methods. The finalizers delete C++ resources tied with
the object. This can lead to segmentation fault.
----- Solution -----
The 'Finalizable' interface is a viable solution proposed by Dart:FFI
documentation. It ensures, that 'this' object outlives the whole method
call -- it guarantees, that the native function call finishes before the
finalizer is run.